Compare commits

...

360 Commits

Author SHA1 Message Date
Owen Schwartz
13cfcb0ffa New translations en-us.json (Spanish)
[ci skip]
2026-05-28 21:18:34 -07:00
Owen Schwartz
e29fe22483 New translations en-us.json (Norwegian Bokmal)
[ci skip]
2026-05-28 21:18:32 -07:00
Owen Schwartz
64e1875d1d New translations en-us.json (Chinese Simplified)
[ci skip]
2026-05-28 21:18:30 -07:00
Owen Schwartz
6417fe55b2 New translations en-us.json (Turkish)
[ci skip]
2026-05-28 21:18:29 -07:00
Owen Schwartz
2cc71bdf5a New translations en-us.json (Russian)
[ci skip]
2026-05-28 21:18:27 -07:00
Owen Schwartz
4bf1999aa9 New translations en-us.json (Portuguese)
[ci skip]
2026-05-28 21:18:25 -07:00
Owen Schwartz
6dbb46316a New translations en-us.json (Polish)
[ci skip]
2026-05-28 21:18:23 -07:00
Owen Schwartz
f0ed0aadf6 New translations en-us.json (Dutch)
[ci skip]
2026-05-28 21:18:21 -07:00
Owen Schwartz
bf28ceb72c New translations en-us.json (Korean)
[ci skip]
2026-05-28 21:18:19 -07:00
Owen Schwartz
799e80cca9 New translations en-us.json (Italian)
[ci skip]
2026-05-28 21:18:17 -07:00
Owen Schwartz
efb3ac103c New translations en-us.json (German)
[ci skip]
2026-05-28 21:18:16 -07:00
Owen Schwartz
3b8b4ad360 New translations en-us.json (Czech)
[ci skip]
2026-05-28 21:18:14 -07:00
Owen Schwartz
24c64eb2b5 New translations en-us.json (Bulgarian)
[ci skip]
2026-05-28 21:18:12 -07:00
Owen Schwartz
b8150dd81c New translations en-us.json (French)
[ci skip]
2026-05-28 21:18:10 -07:00
Owen Schwartz
bb1b815fc5 New translations en-us.json (Spanish)
[ci skip]
2026-05-28 16:17:56 -07:00
Owen Schwartz
b63dbbe28d New translations en-us.json (Norwegian Bokmal)
[ci skip]
2026-05-28 16:17:54 -07:00
Owen Schwartz
9935ba7714 New translations en-us.json (Chinese Simplified)
[ci skip]
2026-05-28 16:17:52 -07:00
Owen Schwartz
18ca08af6e New translations en-us.json (Turkish)
[ci skip]
2026-05-28 16:17:50 -07:00
Owen Schwartz
e68a71138e New translations en-us.json (Russian)
[ci skip]
2026-05-28 16:17:48 -07:00
Owen Schwartz
00d484da5e New translations en-us.json (Portuguese)
[ci skip]
2026-05-28 16:17:46 -07:00
Owen Schwartz
899b901ba3 New translations en-us.json (Polish)
[ci skip]
2026-05-28 16:17:44 -07:00
Owen Schwartz
a0065a41d1 New translations en-us.json (Dutch)
[ci skip]
2026-05-28 16:17:43 -07:00
Owen Schwartz
114593b49b New translations en-us.json (Korean)
[ci skip]
2026-05-28 16:17:41 -07:00
Owen Schwartz
af91ad2bf3 New translations en-us.json (Italian)
[ci skip]
2026-05-28 16:17:39 -07:00
Owen Schwartz
a5676271c7 New translations en-us.json (German)
[ci skip]
2026-05-28 16:17:37 -07:00
Owen Schwartz
214c58c9cb New translations en-us.json (Czech)
[ci skip]
2026-05-28 16:17:35 -07:00
Owen Schwartz
ea1e2edc6d New translations en-us.json (Bulgarian)
[ci skip]
2026-05-28 16:17:33 -07:00
Owen Schwartz
e3bf2be053 New translations en-us.json (French)
[ci skip]
2026-05-28 16:17:31 -07:00
Owen
f8a757c55f Merge branch 'resource-policies' into dev 2026-05-28 15:30:16 -07:00
Owen
6aea3f1643 Merge branch 'auto-update' into dev 2026-05-28 13:59:34 -07:00
Owen
073dc34522 Merge branch 'rdp-ssh' into dev 2026-05-28 13:59:14 -07:00
Owen
3f5970a1f9 Merge branch 'dev' of github.com:fosrl/pangolin into dev 2026-05-28 13:56:18 -07:00
Owen
e2f2608358 Merge branch 'main' into dev 2026-05-28 13:56:08 -07:00
Owen Schwartz
6d17bb04c4 Merge pull request #3167 from shleeable/patch-1
Installer: format main.go
2026-05-28 12:13:45 -07:00
Owen Schwartz
957e7ba127 Merge pull request #3175 from shleeable/patch-4
Fix:  OLM token rate limit uses wrong field name
2026-05-28 12:13:04 -07:00
Owen Schwartz
def710cba8 Merge pull request #3176 from shleeable/patch-5
Fix: Update external.ts windowMs rate limit for milliseconds
2026-05-28 12:12:39 -07:00
Owen Schwartz
44da854575 Merge pull request #3177 from shleeable/patch-6
Fix: Missing return
2026-05-28 12:11:40 -07:00
miloschwartz
d3d2474855 update blueprints to say blueprints log 2026-05-28 12:11:20 -07:00
Owen Schwartz
d7d37c6f6e Merge pull request #3179 from fosrl/dependabot/npm_and_yarn/dev-minor-updates-545c73ecbb
Bump the dev-minor-updates group across 1 directory with 6 updates
2026-05-28 12:10:40 -07:00
dependabot[bot]
3c80b9a229 Bump the dev-minor-updates group across 1 directory with 6 updates
Bumps the dev-minor-updates group with 6 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [@dotenvx/dotenvx](https://github.com/dotenvx/dotenvx) | `1.66.0` | `1.69.1` |
| [@react-email/ui](https://github.com/resend/react-email/tree/HEAD/packages/ui) | `6.1.4` | `6.5.0` |
| [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) | `25.8.0` | `25.9.1` |
| [eslint](https://github.com/eslint/eslint) | `10.3.0` | `10.4.0` |
| [react-email](https://github.com/resend/react-email/tree/HEAD/packages/react-email) | `6.1.4` | `6.5.0` |
| [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint) | `8.59.3` | `8.60.0` |



Updates `@dotenvx/dotenvx` from 1.66.0 to 1.69.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.66.0...v1.69.1)

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

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

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

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

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

---
updated-dependencies:
- dependency-name: "@dotenvx/dotenvx"
  dependency-version: 1.69.1
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: dev-minor-updates
- dependency-name: "@react-email/ui"
  dependency-version: 6.5.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: dev-minor-updates
- dependency-name: "@types/node"
  dependency-version: 25.9.1
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: dev-minor-updates
- dependency-name: eslint
  dependency-version: 10.4.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: dev-minor-updates
- dependency-name: react-email
  dependency-version: 6.5.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: dev-minor-updates
- dependency-name: typescript-eslint
  dependency-version: 8.60.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: dev-minor-updates
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-28 19:10:09 +00:00
Owen Schwartz
a998a35482 Merge pull request #3181 from fosrl/remove-resend
Remove resend
2026-05-28 12:07:20 -07:00
Owen
20e0e5ebd0 Remove resend 2026-05-28 12:06:29 -07:00
Owen Schwartz
4d831effe1 Merge pull request #3180 from fosrl/dependabot/npm_and_yarn/prod-patch-updates-203742b32f
Bump the prod-patch-updates group across 1 directory with 5 updates
2026-05-28 12:06:08 -07:00
dependabot[bot]
80f4dd0e60 Bump the prod-patch-updates group across 1 directory with 5 updates
Bumps the prod-patch-updates group with 5 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [@simplewebauthn/server](https://github.com/MasterKale/SimpleWebAuthn/tree/HEAD/packages/server) | `13.3.0` | `13.3.1` |
| [@tanstack/react-query](https://github.com/TanStack/query/tree/HEAD/packages/react-query) | `5.100.10` | `5.100.14` |
| [nodemailer](https://github.com/nodemailer/nodemailer) | `8.0.7` | `8.0.9` |
| [resend](https://github.com/resend/resend-node) | `6.12.3` | `6.12.4` |
| [semver](https://github.com/npm/node-semver) | `7.8.0` | `7.8.1` |



Updates `@simplewebauthn/server` from 13.3.0 to 13.3.1
- [Release notes](https://github.com/MasterKale/SimpleWebAuthn/releases)
- [Changelog](https://github.com/MasterKale/SimpleWebAuthn/blob/master/CHANGELOG.md)
- [Commits](https://github.com/MasterKale/SimpleWebAuthn/commits/v13.3.1/packages/server)

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

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

Updates `resend` from 6.12.3 to 6.12.4
- [Release notes](https://github.com/resend/resend-node/releases)
- [Commits](https://github.com/resend/resend-node/compare/v6.12.3...v6.12.4)

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

---
updated-dependencies:
- dependency-name: "@simplewebauthn/server"
  dependency-version: 13.3.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: prod-patch-updates
- dependency-name: "@tanstack/react-query"
  dependency-version: 5.100.14
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: prod-patch-updates
- dependency-name: nodemailer
  dependency-version: 8.0.9
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: prod-patch-updates
- dependency-name: resend
  dependency-version: 6.12.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: prod-patch-updates
- dependency-name: semver
  dependency-version: 7.8.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: prod-patch-updates
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-28 19:02:31 +00:00
Owen Schwartz
eafa3076d8 Merge pull request #3137 from fosrl/dependabot/npm_and_yarn/qs-6.15.2
Bump qs from 6.15.1 to 6.15.2
2026-05-28 12:01:50 -07:00
Owen Schwartz
fef3cd8354 Merge pull request #2908 from fosrl/dependabot/github_actions/actions/setup-node-6.4.0
Bump actions/setup-node from 6.3.0 to 6.4.0
2026-05-28 12:00:48 -07:00
Owen Schwartz
36ada0705e Merge pull request #3044 from fosrl/dependabot/github_actions/sigstore/cosign-installer-4.1.2
Bump sigstore/cosign-installer from 4.1.1 to 4.1.2
2026-05-28 12:00:38 -07:00
Owen Schwartz
8ae3c06df7 Merge pull request #3143 from fosrl/dependabot/github_actions/actions/stale-10.3.0
Bump actions/stale from 10.2.0 to 10.3.0
2026-05-28 12:00:25 -07:00
dependabot[bot]
ba127a8536 Bump qs from 6.15.1 to 6.15.2
Bumps [qs](https://github.com/ljharb/qs) from 6.15.1 to 6.15.2.
- [Changelog](https://github.com/ljharb/qs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/ljharb/qs/compare/v6.15.1...v6.15.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-28 18:59:36 +00:00
Owen Schwartz
5c024f3a3a Merge pull request #3142 from fosrl/dependabot/github_actions/docker/login-action-4.2.0
Bump docker/login-action from 4.1.0 to 4.2.0
2026-05-28 11:57:53 -07:00
Owen Schwartz
4fdb8583f6 Merge pull request #3178 from fosrl/sec-updates
Advance security updates to main
2026-05-28 11:56:57 -07:00
Owen Schwartz
2946df3b8e Merge pull request #3085 from marcschaeferger-org/security-updates
Normalize request parameters and update dependencies for Security
2026-05-28 11:54:23 -07:00
Shlee
c3b0c4e5e9 Update verifyApiKeyOrgAccess.ts 2026-05-28 15:55:34 +09:30
Shlee
a79d0f1677 Update external.ts 2026-05-28 15:45:06 +09:30
Shlee
bfd7a7f561 Update external.ts 2026-05-28 15:31:45 +09:30
Owen
a5332bb0cc Update tsconfig 2026-05-27 21:38:19 -07:00
Owen
b3963cc34b Middleware -> proxy 2026-05-27 21:38:12 -07:00
Owen Schwartz
ddb132f9fa Merge pull request #3085 from marcschaeferger-org/security-updates
Normalize request parameters and update dependencies for Security
2026-05-27 21:37:50 -07:00
Owen
64c901d91f Properly lock the ip selection through writes to db 2026-05-27 21:08:45 -07:00
Owen
cd9e56fdb7 Make the destination optional 2026-05-27 17:52:04 -07:00
Owen
1b6b112e92 Add auth daemon to blueprints 2026-05-27 17:29:19 -07:00
Owen
0ff0e83c9f Complete removal of http and protocol from public 2026-05-27 17:19:04 -07:00
Owen
6d491b7bb9 Cache wildcard certs for easy lookup 2026-05-27 14:58:36 -07:00
miloschwartz
cdc50ed47a remove shadow from labels 2026-05-27 14:23:04 -07:00
Owen
06cc13c637 Moving to mode replacing http and protocol fields 2026-05-27 12:04:00 -07:00
Owen
464d4990df Fix cascading errors 2026-05-27 11:34:34 -07:00
miloschwartz
e2441ce284 adjust label overflow 2026-05-26 23:59:49 -07:00
miloschwartz
0b6a3234a5 auto close labels dropdown on select but not on checkbox 2026-05-26 22:47:05 -07:00
miloschwartz
ae8599c723 dont close label filter after select 2026-05-26 22:30:55 -07:00
miloschwartz
938e9b0d49 more ui/ux enhancements around labels and tables 2026-05-26 22:26:54 -07:00
miloschwartz
05e4ad3200 minor visual adjustments to tags 2026-05-26 21:34:15 -07:00
Owen
cb90672573 Trying to get these forms to work 2026-05-26 21:20:34 -07:00
Milo Schwartz
9eb55ba68c Merge pull request #3130 from Fredkiss3/feat/filter-on-label-column
feat: add label filter column to sites, resource & client tables
2026-05-26 21:01:22 -07:00
Owen
e19b6ebc82 Hide the destination and the alias 2026-05-26 20:38:04 -07:00
Owen
5a6de12f74 Revert to the mode on top and make it 2 x 2 2026-05-26 20:30:33 -07:00
Owen
6e6c91a27c Move site down 2026-05-26 20:16:54 -07:00
Shlee
cf12ab1ac3 Update main.go 2026-05-27 12:12:48 +09:30
Owen
aa7004b2ff Add new ssh config for private resources 2026-05-26 17:50:46 -07:00
Owen
eca87b66f0 Use the create api 2026-05-26 17:11:45 -07:00
Owen
cc8c89eeae Cleaning up some react 2026-05-26 16:53:22 -07:00
Fred KISSIE
6d14a4df49 💄 fix blueprint toast color 2026-05-27 01:36:45 +02:00
Owen
6ea4aa1920 Suppoter key 2026-05-26 16:35:28 -07:00
Owen
f12451b8f9 Consolidate target components 2026-05-26 16:33:54 -07:00
Owen
0d4bb65a92 Adjusting the create ui 2026-05-26 16:10:06 -07:00
Owen
d47ad9ac40 Fix height problem 2026-05-26 16:08:52 -07:00
Fred KISSIE
94949aa3fd ♻️ fix search params on other tables too 2026-05-27 00:44:18 +02:00
Fred KISSIE
df098f55ba ♻️ pass default user search params query to user devices table 2026-05-27 00:23:48 +02:00
Owen
f81ae24ba7 Clean up the ui a bit 2026-05-26 15:05:30 -07:00
Fred KISSIE
facbb8f0a4 label filter column on the clients table 2026-05-26 23:46:56 +02:00
Fred KISSIE
36fbd8818c label filter column for private resources 2026-05-26 23:36:07 +02:00
Owen
df1e28aabd Pass one on the new create screen 2026-05-26 14:29:13 -07:00
Fred KISSIE
91883397e6 label filter column 2026-05-26 22:45:41 +02:00
Fred KISSIE
fd1813f3a7 Merge branch 'dev' into feat/filter-on-label-column 2026-05-26 22:26:18 +02:00
Owen Schwartz
ddabfb5ca1 Merge pull request #3154 from RitwijParmar/codex/pangolin-refresh-live-log-window
fix(logs): refresh default end time
2026-05-26 11:52:10 -07:00
Owen Schwartz
ec0666a612 Merge pull request #3151 from shleeable/patch-1
Installer: Handle both Maxmind Country and ASN databases.
2026-05-26 09:50:08 -07:00
Shlee
bbf42c5802 Update main.go 2026-05-26 17:14:06 +09:30
Ritwij Aryan Parmar
6aa1d3b094 fix(logs): refresh default end time 2026-05-26 01:26:53 -04:00
miloschwartz
0d820df797 add ce or ee to issue template 2026-05-25 21:40:02 -07:00
Shlee
f1ec1a2fb1 Update docker-compose.yml 2026-05-26 13:49:06 +09:30
Shlee
32fcf90467 Update docker-compose.yml 2026-05-26 13:48:00 +09:30
Shlee
5a53f88fd6 Update main.go 2026-05-26 13:37:28 +09:30
Shlee
51971c7ef2 Update config.yml 2026-05-26 13:36:01 +09:30
Shlee
491096109a Update main.go 2026-05-26 13:31:07 +09:30
Shlee
802a41b1bd Update main.go 2026-05-26 13:25:53 +09:30
Shlee
f59fbabede Update main.go 2026-05-26 13:12:48 +09:30
Shlee
5a7d54058e Update main.go 2026-05-26 13:06:35 +09:30
Owen Schwartz
5ef4490692 Merge pull request #3148 from bishnubista/fix-audit-log-replica-routing
fix(audit-logs): route request audit log reads through logsDb
2026-05-25 12:02:24 -07:00
bishnubista
817e848d08 fix(audit-logs): route request audit log reads through logsDb
Route the read paths in queryRequestAuditLog.ts and
queryRequestAnalytics.ts through `logsDb` instead of
`primaryLogsDb`, matching the existing private audit log routes
(queryActionAuditLog, queryAccessAuditLog, queryConnectionAuditLog
all already use `logsDb`). In PostgreSQL deployments configured
with a read replica via `withReplicas` (see server/db/pg/logsDriver.ts),
this keeps high-volume audit log reads off the primary. No-op
in OSS-SQLite where `logsDb === primaryDb`.

Investigated rewriting `queryUniqueFilterAttributes` per the
in-line TODO ("SOMEONE PLEASE OPTIMIZE THIS!!!!!"). A candidate
rewrite using UNION ALL with six GROUP BY...LIMIT 500 arms
benchmarked 48-61% slower than the current SELECT DISTINCT
LIMIT 501 approach on SQLite (100k/300k/1M rows, 20 runs each):
each grouped arm materializes a temp B-tree before applying LIMIT,
while DISTINCT short-circuits via hash dedup with early exit.
A materialized facets table is likely the right long-term fix,
not a query-shape rewrite.
2026-05-25 10:37:47 -07:00
dependabot[bot]
166c8326c5 Bump actions/stale from 10.2.0 to 10.3.0
Bumps [actions/stale](https://github.com/actions/stale) from 10.2.0 to 10.3.0.
- [Release notes](https://github.com/actions/stale/releases)
- [Changelog](https://github.com/actions/stale/blob/main/CHANGELOG.md)
- [Commits](b5d41d4e1d...eb5cf3af3a)

---
updated-dependencies:
- dependency-name: actions/stale
  dependency-version: 10.3.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-25 01:52:46 +00:00
dependabot[bot]
673f1e93f4 Bump docker/login-action from 4.1.0 to 4.2.0
Bumps [docker/login-action](https://github.com/docker/login-action) from 4.1.0 to 4.2.0.
- [Release notes](https://github.com/docker/login-action/releases)
- [Commits](4907a6ddec...650006c6eb)

---
updated-dependencies:
- dependency-name: docker/login-action
  dependency-version: 4.2.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-25 01:52:42 +00:00
Owen
4c1e1daf07 All page types are there and look mostly correct 2026-05-22 17:37:37 -07:00
Owen
7c54df7ed1 Rework page to be functional 2026-05-22 16:09:02 -07:00
Owen
9d77fcc457 Make the first ssh page and conditional http page 2026-05-22 15:12:37 -07:00
Owen
454449ec8a Add support for push pam users 2026-05-22 12:12:55 -07:00
Owen
fe67e8e384 Clean up types 2026-05-22 12:12:46 -07:00
Owen
715b957660 Support not push ssh method 2026-05-22 11:19:35 -07:00
Owen
f1e4bf8d36 Add hint about invoices for the license key 2026-05-22 10:30:41 -07:00
Fred KISSIE
76aea311a4 add label filter column to sitesTable 2026-05-22 04:07:49 +02:00
Owen
3539b9ddb4 Working 2026-05-21 17:30:06 -07:00
Owen Schwartz
1a3cf2094b Merge pull request #3117 from Fredkiss3/refactor/loading-animation-on-request-logs
feat: Show a loading animation on http request logs table
2026-05-21 17:28:20 -07:00
Owen
4530aac4f3 Update setting is working
Adjust the ui

Adjust description
2026-05-21 16:34:32 -07:00
Fred KISSIE
09cb20a084 push 2026-05-22 00:44:29 +02:00
Owen
6d4afd0953 Control updates from the ui 2026-05-21 15:43:31 -07:00
Owen
d1fb2e19d3 Fix cache import to be dynamic 2026-05-21 14:43:50 -07:00
Owen
dee0ca6864 Add permissions check, shasum check, & build info 2026-05-21 14:34:16 -07:00
Fred KISSIE
2934bbdd20 ♻️ use react query for network logs 2026-05-21 23:27:02 +02:00
Fred KISSIE
2b46e8eaba ♻️ use useQuery for network action logs 2026-05-21 23:23:49 +02:00
Owen
ed73d089d0 Auto update newt 2026-05-21 14:13:32 -07:00
Owen
3b89104a59 Add regional redis cache 2026-05-21 14:07:09 -07:00
Fred KISSIE
5bf8b336c5 ♻️ useQuery for fetching access logs 2026-05-21 23:05:34 +02:00
Fred KISSIE
21a144753d Add access react query 2026-05-21 22:50:15 +02:00
Fred KISSIE
c1b8dfc863 ♻️ refactor 2026-05-21 22:44:24 +02:00
Fred KISSIE
5efcd4479a Merge branch 'dev' into refactor/loading-animation-on-request-logs 2026-05-21 22:31:16 +02:00
Owen
e4e8b33e9f Enforce absolute paths for sudo commands 2026-05-21 12:14:52 -07:00
Owen Schwartz
35ad235f49 Merge pull request #3129 from fosrl/fix-site-delete
Improve delete function speed & order of ops
2026-05-21 12:06:18 -07:00
Owen
af13790c93 Fix pasting the device code not working 2026-05-20 16:28:12 -07:00
Owen
87bcd8ec1b Merge branch 'main' into dev 2026-05-20 15:59:01 -07:00
Owen
23ca3efbf4 Merge branch 'dev' into rdp-ssh 2026-05-19 20:12:05 -07:00
Owen
0f9100fd3a Merge branch 'rdp-ssh' of github.com:fosrl/pangolin into rdp-ssh 2026-05-19 20:06:48 -07:00
Owen Schwartz
c47c411161 Merge pull request #3114 from Fredkiss3/fix/tag-input-scroll
fix: make tag input wrap around instead of scrolling
2026-05-19 20:03:59 -07:00
Owen Schwartz
e88e262abe Merge pull request #3004 from Fredkiss3/feat/labels-on-sites-and-resources
feat: site & resource labels
2026-05-19 20:03:22 -07:00
Owen
832d45e32b Move pages back 2026-05-19 20:02:27 -07:00
Owen
69e3ac3cd4 Move login page locations 2026-05-19 20:02:27 -07:00
Owen
50865f4265 Remove terminate button 2026-05-19 20:02:27 -07:00
Owen
0d1a8d9695 Only switch if we are actually connected 2026-05-19 20:02:27 -07:00
Owen
5d8486dd7f Sure up some things with browserAccessType 2026-05-19 20:02:27 -07:00
Owen
3c25932787 Adjust page to be editable 2026-05-19 20:02:27 -07:00
Owen
1d0e1eb126 Temp credential storage 2026-05-19 20:02:27 -07:00
Owen
57c0dc8618 Support private key 2026-05-19 20:02:27 -07:00
Owen
526a147570 Clean up toasts 2026-05-19 20:02:27 -07:00
Owen
0938997548 Add crud for browser targets 2026-05-19 20:02:27 -07:00
Owen
0876b482f8 Remove extra fields 2026-05-19 20:02:27 -07:00
Owen
d558c31f88 Standardize the ui 2026-05-19 20:02:26 -07:00
Owen
6010515da0 Pull in the destination from the api 2026-05-19 20:02:26 -07:00
Owen
868bcd8e34 USe right table 2026-05-19 20:02:26 -07:00
Owen
20c4904965 Add internal api get for proxy information 2026-05-19 20:02:26 -07:00
Owen
5a5536b38c Reinstall packages 2026-05-19 20:02:26 -07:00
Owen
53e2296de8 Clean up forms a bit 2026-05-19 20:02:26 -07:00
Owen
d2423919e9 Add favicon passthrough 2026-05-19 20:02:26 -07:00
Owen
2250fcd177 Serve the resource from the right place 2026-05-19 20:02:26 -07:00
Owen
2a33256d17 Add gateway endpoints into the traefik config 2026-05-19 20:02:26 -07:00
Owen
117aa750f8 Working on new target type 2026-05-19 20:02:26 -07:00
Owen
15f161274f Add browserGatewayTarget table 2026-05-19 20:02:26 -07:00
Owen
09779aca3e Add basic vnc test 2026-05-19 20:02:25 -07:00
Owen
1d1f7cecf4 Support rdp 2026-05-19 20:02:25 -07:00
Owen
dc00668cbe Add first iteration of ssh proxy 2026-05-19 20:02:25 -07:00
Owen
57701e13eb Comment out some fields 2026-05-19 20:02:25 -07:00
Owen
46545cb003 Initial rdp working 2026-05-19 20:02:21 -07:00
Fred KISSIE
a163cc3678 💄 show loading animation on http request logs table 2026-05-20 04:50:49 +02:00
Fred KISSIE
1dfb3408e8 ♻️ use react query for fetching instead of useEfffect 2026-05-20 01:00:56 +02:00
Fred KISSIE
67fb2beba1 Merge branch 'dev' into refactor/loading-animation-on-request-logs 2026-05-19 23:54:21 +02:00
Fred KISSIE
6cacc9b83f 💄 limit tag width 2026-05-19 22:52:44 +02:00
Fred KISSIE
1f1791feb7 💄 make tag input wrap around instead of scrolling 2026-05-19 22:48:15 +02:00
Fred KISSIE
c500979099 Merge branch 'dev' into refactor/loading-animation-on-request-logs 2026-05-18 22:52:27 +02:00
Fred KISSIE
2d9c082607 💄 UI 2026-05-18 22:17:49 +02:00
Fred KISSIE
7968c4357b edit org label 2026-05-18 22:14:49 +02:00
Fred KISSIE
25c08e7279 Create label dialog 2026-05-18 21:57:44 +02:00
dependabot[bot]
e4fd2b656d Bump sigstore/cosign-installer from 4.1.1 to 4.1.2
Bumps [sigstore/cosign-installer](https://github.com/sigstore/cosign-installer) from 4.1.1 to 4.1.2.
- [Release notes](https://github.com/sigstore/cosign-installer/releases)
- [Commits](https://github.com/sigstore/cosign-installer/compare/v4.1.1...6f9f17788090df1f26f669e9d70d6ae9567deba6)

---
updated-dependencies:
- dependency-name: sigstore/cosign-installer
  dependency-version: 4.1.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-16 21:55:15 +00:00
Owen
987b5d580e Sure up some things with browserAccessType 2026-05-15 17:26:58 -07:00
Owen
cb75ffc3b7 Adjust page to be editable 2026-05-15 16:46:43 -07:00
Owen
540f0a754d Temp credential storage 2026-05-15 16:11:23 -07:00
Owen
0f9a6fd968 Support private key 2026-05-15 16:07:14 -07:00
Owen
82112abc34 Clean up toasts 2026-05-15 15:01:37 -07:00
Owen
75b5afd544 Add crud for browser targets 2026-05-15 14:09:33 -07:00
Owen
00e1675f7b Remove extra fields 2026-05-15 12:08:40 -07:00
Owen
2ddbdf977b Standardize the ui 2026-05-15 12:06:05 -07:00
Owen
4c8f0cc9ec Pull in the destination from the api 2026-05-15 11:48:13 -07:00
Marc Schäfer
18d380ce30 fix(security): normalize request parameters and update dependencies
Signed-off-by: Marc Schäfer <git@marcschaeferger.de>
2026-05-15 18:35:58 +00:00
Owen
e822b681cd Merge branch 'dev' into rdp-ssh 2026-05-15 11:18:31 -07:00
Fred KISSIE
68d7b0a416 🚧 wip: label 2026-05-14 22:43:29 +02:00
Fred KISSIE
43546c84eb 🚧 wip: create label dialog 2026-05-14 22:42:01 +02:00
Fred KISSIE
eac36ee442 delete label 2026-05-14 22:15:43 +02:00
Fred KISSIE
9a88394efe 🛂 gate label endpoints behing subscription 2026-05-14 21:17:58 +02:00
Fred KISSIE
173562654b delete org label endpoint 2026-05-14 21:09:48 +02:00
Owen
e1583a58aa USe right table 2026-05-14 11:33:42 -07:00
Fred KISSIE
8f7e5ab1ed 🚧 wip: org labels page 2026-05-14 19:31:53 +02:00
Fred KISSIE
4334480675 ♻️ refactor 2026-05-14 18:33:29 +02:00
Fred KISSIE
6aa406927a 🐛 fix error message 2026-05-14 18:20:26 +02:00
Fred KISSIE
5b50024712 Merge branch 'dev' into feat/labels-on-sites-and-resources 2026-05-14 18:15:14 +02:00
Owen
7d922ac95f Add internal api get for proxy information 2026-05-13 21:54:58 -07:00
Owen
795a3d351e Reinstall packages 2026-05-13 21:16:40 -07:00
Owen
4b4c86b4b7 Clean up forms a bit 2026-05-13 21:16:00 -07:00
Owen
013af49137 Add favicon passthrough 2026-05-13 21:11:25 -07:00
Owen
a6ae9290f2 Serve the resource from the right place 2026-05-13 18:01:36 -07:00
Owen
de70d72e0d Add gateway endpoints into the traefik config 2026-05-13 17:33:16 -07:00
Owen
4e07e9c52c Working on new target type 2026-05-13 11:56:23 -07:00
Owen
743621eb25 Add browserGatewayTarget table 2026-05-12 21:48:59 -07:00
Owen
e9df995e76 Merge branch 'dev' into resource-policies 2026-05-12 21:12:40 -07:00
Owen
943923ff4b Add basic vnc test 2026-05-12 21:12:01 -07:00
Owen
3f17f1a468 Support rdp 2026-05-12 21:12:01 -07:00
Owen
436996a43d Add first iteration of ssh proxy 2026-05-12 21:12:01 -07:00
Owen
d42b6076d2 Comment out some fields 2026-05-12 21:12:01 -07:00
Owen
89cc99f915 Initial rdp working 2026-05-12 21:12:00 -07:00
Fred KISSIE
ce746a2a21 Handle labels for machine clients 2026-05-12 22:32:56 +02:00
Fred KISSIE
7120ab4b22 ♻️ filter sites & resources by labels 2026-05-12 20:45:12 +02:00
Fred KISSIE
12e777b32e Add labels column to private resources table 2026-05-12 20:25:32 +02:00
Fred KISSIE
9378103ddd handle private resources filtering by labels 2026-05-12 20:24:34 +02:00
Fred KISSIE
ec794d5de2 attach/detach private resources 2026-05-12 20:01:33 +02:00
Fred KISSIE
12b18a3e8c attach labels to private resources 2026-05-12 19:58:44 +02:00
Fred KISSIE
91e8a13e59 🗃️ Add site resource labels schema 2026-05-12 17:55:56 +02:00
Fred KISSIE
931ba0f540 💄 px-2 button 2026-05-12 17:46:46 +02:00
Fred KISSIE
d321d7275c 🚧 tried to memo proxy resource table, failed 2026-05-11 21:06:20 +02:00
Fred KISSIE
3855486a00 ️ prevent SitetableCell from rerendering unnecessarily 2026-05-11 19:27:00 +02:00
Fred KISSIE
ab494521b1 labels on proxy resources 2026-05-11 18:37:16 +02:00
Fred KISSIE
549e1ead1d handle labels in resources too 2026-05-11 18:30:23 +02:00
Fred KISSIE
a0759a79a1 🗃️ add unique indexes to site & resource labels in sqlite 2026-05-11 18:28:40 +02:00
Fred KISSIE
14e1a119d3 🚧 WIP: showing labels in proxy resources table 2026-05-11 18:24:47 +02:00
Fred KISSIE
6e066d38b0 🚚 Make label badge its own component 2026-05-11 18:17:29 +02:00
Fred KISSIE
21f72639b6 🚧 make labels column paid, and cleanup 2026-05-11 18:13:19 +02:00
Fred KISSIE
8a0c2031d4 search list by labels too 2026-05-11 18:02:59 +02:00
Fred KISSIE
56d3a466e5 💄 make controlled data table input a search input 2026-05-11 18:02:44 +02:00
Fred KISSIE
563e505cc1 💸 add labels to paid features 2026-05-11 18:02:15 +02:00
Fred KISSIE
c44c02b8ba 💄 make site labels column design nicer 2026-05-11 17:04:44 +02:00
Fred KISSIE
b9ab35a05b 🐛 handle idempotency when adding/removing labels from sites/resources 2026-05-11 16:57:53 +02:00
Fred KISSIE
2fd519e102 add and toggle site labels 2026-05-08 22:31:36 +02:00
Fred KISSIE
a63c1ec364 💄 label selector (with create label) 2026-05-08 21:49:20 +02:00
Fred KISSIE
e61ef2ca2a 🚧 wip: label selector 2026-05-08 20:06:42 +02:00
Fred KISSIE
39b09b7f3f Merge branch 'dev' into feat/labels-on-sites-and-resources 2026-05-08 18:21:46 +02:00
Fred KISSIE
840cc214e3 🚧 wip 2026-05-08 18:21:09 +02:00
Fred KISSIE
72524db52d 💄 shrink button 2026-05-08 02:48:47 +02:00
Fred KISSIE
ab8fc11ab3 🚧 add labels button 2026-05-08 02:46:16 +02:00
Fred KISSIE
1831ca4e75 ♻️ detach label from site/resoirce 2026-05-08 00:33:47 +02:00
Owen
c4b3656fad Update UI to support additions on the resource 2026-05-06 10:09:05 -07:00
Owen
54c1dd3bae Make path the default 2026-05-05 21:05:42 -07:00
Owen
a8f4d2b7d1 Add new user and role selectors for pagination 2026-05-05 20:53:36 -07:00
Owen
51f1693dbd Merge branch 'dev' into resource-policies 2026-05-05 18:02:27 -07:00
Fred KISSIE
0d04cc365f attach label to item 2026-05-05 21:35:10 +02:00
Fred KISSIE
09baf2f32e 🗃️ add sqlite table for labels 2026-05-05 21:08:22 +02:00
Fred KISSIE
3253d60900 🚧 Add CRUD endpoints and tables for labels 2026-05-05 20:53:16 +02:00
Owen
b33a6e6fac Wipe the old tables if you are using inline 2026-05-04 20:54:43 -07:00
Owen
fc2c13a686 Add policies to blueprints 2026-05-04 20:44:04 -07:00
Owen
f4602a120e Merge branch 'dev' into resource-policies 2026-05-04 17:57:09 -07:00
Owen
7ccceeea0d Ignore extra sqlite files 2026-05-04 17:43:02 -07:00
Owen
f81f78f294 Merge branch 'dev' into resource-policies 2026-05-04 17:41:49 -07:00
Owen
6cab223f12 Adjust verify session queries to use policies 2026-05-04 17:30:10 -07:00
Owen
7b05c02508 Adjust translation 2026-05-04 16:19:04 -07:00
Owen
5922bfb1a0 Fix API endpoint action issues 2026-05-04 16:01:40 -07:00
Owen
43f2e32231 Paywall resource policies 2026-05-04 15:30:49 -07:00
Owen
20ebdc6289 Fix openapi zod issue error 2026-05-04 15:04:54 -07:00
Owen
a80ae49a33 Support multiple roles 2026-05-04 14:54:20 -07:00
Owen
660197eef1 Merge branch 'feat/resource-policies' into resource-policies 2026-05-04 14:40:44 -07:00
Fred KISSIE
81274960f6 🚧 refactor 2026-05-04 20:22:16 +02:00
dependabot[bot]
f286d66cbc Bump actions/setup-node from 6.3.0 to 6.4.0
Bumps [actions/setup-node](https://github.com/actions/setup-node) from 6.3.0 to 6.4.0.
- [Release notes](https://github.com/actions/setup-node/releases)
- [Commits](53b83947a5...48b55a011b)

---
updated-dependencies:
- dependency-name: actions/setup-node
  dependency-version: 6.4.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-27 01:36:02 +00:00
Fred KISSIE
f3eb823bc3 🐛 fix sqlite tables 2026-03-12 22:36:29 +01:00
Fred KISSIE
61c13db090 Merge branch 'dev' into feat/resource-policies 2026-03-12 22:19:37 +01:00
Fred KISSIE
ccbd793f52 💬 show error 2026-03-12 22:13:27 +01:00
Fred KISSIE
d13e6896a8 ♻️ update 2026-03-12 22:11:39 +01:00
Fred KISSIE
83a36ead10 ♻️ show success toast on resource policy update 2026-03-12 20:22:16 +01:00
Fred KISSIE
b61b74b0b5 💬 update text 2026-03-12 20:04:02 +01:00
Fred KISSIE
01b068c50f ♻️ do not edit tags if readonly 2026-03-12 18:53:18 +01:00
Fred KISSIE
fee44ce960 navigate to policy to edit 2026-03-12 18:52:13 +01:00
Fred KISSIE
1906504a86 update shared policy when selected 2026-03-12 18:35:50 +01:00
Fred KISSIE
36bcba332c 🚧 wip 2026-03-11 05:18:22 +01:00
Fred KISSIE
304ab1964c 🚧 wip 2026-03-11 04:21:55 +01:00
Fred KISSIE
b286096c7b 🌐 text 2026-03-11 03:47:31 +01:00
Fred KISSIE
a22a4b6e74 ♻️ mark forms as readonly 2026-03-11 03:47:15 +01:00
Fred KISSIE
9a680d2374 update resource should update policy 2026-03-11 03:46:40 +01:00
Fred KISSIE
f80e212b07 🚧 wip 2026-03-11 00:27:27 +01:00
Fred KISSIE
8a39b3fd45 🙈 do not include solo.yml to git 2026-03-10 18:55:12 +01:00
Fred KISSIE
61ec938b00 🚧 WIP 2026-03-10 18:54:26 +01:00
Fred KISSIE
6686de6788 ♻️ refactor 2026-03-10 17:48:17 +01:00
Fred KISSIE
79636cbb30 ♻️ delete default resource policy ID when deleting a resource 2026-03-10 17:38:19 +01:00
Fred KISSIE
2fa1bc6cdc 🚧 wip 2026-03-07 03:55:30 +01:00
Fred KISSIE
c5f6d822ca ♻️ refactor auth info to use resource policies 2026-03-07 03:45:10 +01:00
Fred KISSIE
4de4bf9625 use resource policies for auth check 2026-03-07 03:35:26 +01:00
Fred KISSIE
5d956080f2 create default policy when creating a resource 2026-03-07 02:29:36 +01:00
Fred KISSIE
f8e18de2fc ♻️ prevent deleting resource policies if they have attached resources 2026-03-07 01:12:10 +01:00
Fred KISSIE
884482ec35 ♻️ delete resource policy endpoint 2026-03-06 23:57:23 +01:00
Fred KISSIE
9b43948fa4 delete resource policy endpoint 2026-03-06 22:39:44 +01:00
Fred KISSIE
bcd6cd99cc 🚧 wip 2026-03-06 04:37:57 +01:00
Fred KISSIE
37ceba6b81 💄 show attached resources in policy list 2026-03-06 04:36:12 +01:00
Fred KISSIE
dfe42e9016 ♻️ refactor 2026-03-06 04:03:40 +01:00
Fred KISSIE
38aa2dace8 ♻️ show list of resources on policy list 2026-03-06 04:03:25 +01:00
Fred KISSIE
136c3eff0c ♻️ padding bottom 2026-03-05 19:46:16 +01:00
Fred KISSIE
642999c8b1 ♻️ separate create form into multiple ones 2026-03-05 19:45:13 +01:00
Fred KISSIE
c5fc49b4fa 🚧 wip 2026-03-05 19:31:19 +01:00
Fred KISSIE
cd5a38b1eb 🚧 WIP: create policy form 2026-03-05 18:56:35 +01:00
Fred KISSIE
595842c2c9 finish create policy endpoint 2026-03-05 18:48:33 +01:00
Fred KISSIE
82d5276ade 🚧 wip: create resource policy 2026-03-05 18:24:04 +01:00
Fred KISSIE
51eb782831 🚧 wip 2026-03-05 18:14:46 +01:00
Fred KISSIE
de2980e1bc apply rules on resource policies 2026-03-05 18:13:30 +01:00
Fred KISSIE
8a3c0d9a08 ♻️ add openapi schema types 2026-03-05 17:51:55 +01:00
Fred KISSIE
1a5e9f1005 🚧 resource policy rules 2026-03-04 19:31:59 +01:00
Fred KISSIE
f42c013f33 ♻️ refactor 2026-03-04 17:41:55 +01:00
Fred KISSIE
42c9bda939 Merge branch 'dev' into feat/resource-policies 2026-03-04 16:46:33 +01:00
Fred KISSIE
cbce9fae3a 🚧 wip 2026-03-04 16:36:49 +01:00
Fred KISSIE
e44b15ecd5 set opt email whitelist 2026-03-04 01:54:50 +01:00
Fred KISSIE
7f6ca31757 🚧 Email whiteList for resource policy 2026-03-04 01:46:56 +01:00
Fred KISSIE
a1eb248474 🔨 remove docker compose mail 2026-03-04 01:10:48 +01:00
Fred KISSIE
be2b1fd1ce 🚧 wip: email whitelist 2026-03-03 20:26:17 +01:00
Fred KISSIE
20b65f549e Update resource policy pincode 2026-03-03 19:49:24 +01:00
Fred KISSIE
1dc8be373c 🚧 wip: add password 2026-03-03 18:54:35 +01:00
Fred KISSIE
22b2e6b3d4 🚧 wip: separating form sections 2026-03-03 18:41:04 +01:00
Fred KISSIE
89e7107a47 ♻️ use put and return 200 OK 2026-03-03 03:31:43 +01:00
Fred KISSIE
0a69131c38 ♻️ merge header auth & extended compability to one table 2026-03-03 03:27:02 +01:00
Fred KISSIE
590f2c29b3 🚧 prepare tables for auth methods 2026-03-03 03:20:03 +01:00
Fred KISSIE
0ddcce6fe1 🗃️ create resource policy specific tables for auth methods 2026-03-03 02:47:21 +01:00
Fred KISSIE
8a54fb7f23 🚧 auth methods 2026-03-03 02:11:05 +01:00
Fred KISSIE
5c280b024e update policy access control 2026-03-03 01:33:37 +01:00
Fred KISSIE
033cc62ce7 🚧 wip 2026-03-02 19:37:23 +01:00
Fred KISSIE
4c69b7a64e update policy access control 2026-03-02 19:26:51 +01:00
Fred KISSIE
e7ab9b3f37 🚧 wip 2026-03-02 18:32:08 +01:00
Fred KISSIE
3143662f82 Merge branch 'dev' into feat/resource-policies 2026-03-02 15:53:00 +01:00
Fred KISSIE
18964ba2a3 🚧 wip 2026-02-28 14:22:41 +01:00
Fred KISSIE
f862404c5c Merge branch 'dev' into feat/resource-policies 2026-02-28 01:17:51 +01:00
Fred KISSIE
c292578f80 Merge branch 'dev' into feat/resource-policies 2026-02-28 01:08:12 +01:00
Fred KISSIE
7b02d4104d 🚧 wip 2026-02-28 00:47:27 +01:00
Fred KISSIE
2ef5d90e13 ♻️ update policy in integration API 2026-02-27 04:24:33 +01:00
Fred KISSIE
d6a8021613 🚧 wip: update resource policy form 2026-02-27 04:21:20 +01:00
Fred KISSIE
c5231d37f6 🚧 wip 2026-02-26 19:20:15 +01:00
Fred KISSIE
4d803a40c9 🚧 wip 2026-02-25 06:00:19 +01:00
Fred KISSIE
1d709b551a create policy endpoitn 2026-02-24 06:31:43 +01:00
Fred KISSIE
335411de4c ♻️ create table for resource policies associations with users 2026-02-24 03:05:51 +01:00
Fred KISSIE
0e4abdf4b6 ♻️ usewatch 2026-02-20 02:06:23 +01:00
Fred KISSIE
267b40b73c 🚧 wip 2026-02-19 05:27:05 +01:00
Fred KISSIE
ba9a0c5e3c ♻️ refactor 2026-02-19 05:23:20 +01:00
Fred KISSIE
9e0b7ff0d7 ♻️ some other ux changes 2026-02-19 05:22:06 +01:00
Fred KISSIE
003bf7fdf3 🚸 hide otp, rules and resource rules config by default 2026-02-19 04:59:51 +01:00
Fred KISSIE
c3fdda026b ♻️ separate into diff components 2026-02-19 04:36:42 +01:00
Fred KISSIE
a53363d064 💄 include rules in create policy form 2026-02-19 03:23:54 +01:00
Fred KISSIE
ee21e1faa7 🚧 list authentication items from policy APIs 2026-02-18 05:08:42 +01:00
Fred KISSIE
e409a34a09 🚧 create policy form 2026-02-18 05:08:27 +01:00
Fred KISSIE
7177ab7f77 🚧 create resource policy table 2026-02-14 05:08:41 +01:00
Fred KISSIE
801f6fb661 🚚 move policies page to (private) folder 2026-02-14 05:03:40 +01:00
Fred KISSIE
805d82b8d9 policies table 2026-02-14 04:59:35 +01:00
Fred KISSIE
bd6d790495 Merge branch 'refactor/paginated-tables' into feat/resource-policies 2026-02-14 04:25:43 +01:00
Fred KISSIE
2305163474 🚧 wip 2026-02-14 03:24:01 +01:00
Fred KISSIE
dda53dcb16 Merge branch 'refactor/paginated-tables' into feat/resource-policies 2026-02-13 06:05:32 +01:00
Fred KISSIE
2c3e768867 🚧 wip: list resource endpoints finished 2026-02-13 05:54:45 +01:00
Fred KISSIE
8d682ed9ad 🚧 list policies endpoint + list policies table 2026-02-13 05:39:35 +01:00
Fred KISSIE
47fe497ca1 🚧 add sidebar item for policies 2026-02-13 05:39:16 +01:00
Fred KISSIE
4d5f364663 ♻️ use the correct types 2026-02-13 05:38:57 +01:00
Fred KISSIE
c3db8b972f ♻️ schema updates for policies 2026-02-13 05:36:42 +01:00
Fred KISSIE
cfced63ba1 Merge branch 'dev' into feat/resource-policies 2026-02-13 02:14:14 +01:00
Fred KISSIE
51aa55f963 revert changes already included in another PR 2026-02-13 00:25:00 +01:00
Fred KISSIE
e7df24841e ♻️ update sqlite DB 2026-02-12 03:50:30 +01:00
Fred KISSIE
e6fd4c32c4 ♻️ update DB 2026-02-12 03:50:09 +01:00
Fred KISSIE
f6590aedbd ♻️ add default sso: true to resource policy table 2026-02-12 03:22:24 +01:00
Fred KISSIE
3cb9e02533 ♻️ make resourcePolicyId non nullable 2026-02-12 02:56:45 +01:00
Fred KISSIE
4d792350ef 🗃️ add resource policy table 2026-02-12 02:53:04 +01:00
264 changed files with 33186 additions and 11075 deletions

View File

@@ -14,12 +14,13 @@ body:
label: Environment
description: Please fill out the relevant details below for your environment.
value: |
- OS Type & Version: (e.g., Ubuntu 22.04)
- OS Type & Version:
- Pangolin Version:
- Edition (Community or Enterprise):
- Gerbil Version:
- Traefik Version:
- Newt Version:
- Olm Version: (if applicable)
- Client Version:
validations:
required: true

View File

@@ -77,7 +77,7 @@ jobs:
fi
- name: Log in to Docker Hub
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0
with:
registry: docker.io
username: ${{ secrets.DOCKER_HUB_USERNAME }}
@@ -149,7 +149,7 @@ jobs:
fi
- name: Log in to Docker Hub
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0
with:
registry: docker.io
username: ${{ secrets.DOCKER_HUB_USERNAME }}
@@ -204,7 +204,7 @@ jobs:
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Log in to Docker Hub
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0
with:
registry: docker.io
username: ${{ secrets.DOCKER_HUB_USERNAME }}
@@ -407,7 +407,7 @@ jobs:
shell: bash
- name: Login to GitHub Container Registry (for cosign)
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0
with:
registry: ghcr.io
username: ${{ github.actor }}

View File

@@ -24,7 +24,7 @@ jobs:
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Set up Node.js
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: '24'

View File

@@ -23,7 +23,7 @@ jobs:
skopeo --version
- name: Install cosign
uses: sigstore/cosign-installer@cad07c2e89fa2edd6e2d7bab4c1aa38e53f76003 # v4.1.1
uses: sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6 # v4.1.2
- name: Input check
run: |

View File

@@ -14,7 +14,7 @@ jobs:
stale:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@b5d41d4e1d5dceea10e7104786b73624c18a190f # v10.2.0
- uses: actions/stale@eb5cf3af3ac0a1aa4c9c45633dd1ae542a27a899 # v10.3.0
with:
days-before-stale: 14
days-before-close: 14

View File

@@ -17,7 +17,7 @@ jobs:
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Install Node
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: '24'

5
.gitignore vendored
View File

@@ -17,9 +17,9 @@ yarn-error.log*
*.tsbuildinfo
next-env.d.ts
*.db
*.sqlite
*.sqlite*
!Dockerfile.sqlite
*.sqlite3
*.sqlite3*
*.log
.machinelogs*.json
*-audit.json
@@ -54,3 +54,4 @@ hydrateSaas.ts
CLAUDE.md
drizzle.config.ts
server/setup/migrations.ts
solo.yml

110
cloud Normal file
View File

@@ -0,0 +1,110 @@
git push origin -d 1.11.0-s.0
git push origin -d 1.11.0-s.1
git push origin -d 1.11.0-s.2
git push origin -d 1.11.0-s.3
git push origin -d 1.11.0-s.4
git push origin -d 1.11.0-s.5
git push origin -d 1.11.1-s.0
git push origin -d 1.12.0-s.0
git push origin -d 1.12.2-s.0
git push origin -d 1.12.2-s.1
git push origin -d 1.12.2-s.2
git push origin -d 1.12.2-s.3
git push origin -d 1.12.2-s.4
git push origin -d 1.12.2-s.5
git push origin -d 1.13.0.s.0
git push origin -d 1.13.1-s.0
git push origin -d 1.14.0-s.2
git push origin -d 1.14.1-s.0
git push origin -d 1.14.1-s.1
git push origin -d 1.14.1-s.2
git push origin -d 1.14.1-s.3
git push origin -d 1.15.0-s.0
git push origin -d 1.15.0-s.1
git push origin -d 1.15.0-s.2
git push origin -d 1.15.0-s.3
git push origin -d 1.15.0-s.4
git push origin -d 1.15.0-s.5
git push origin -d 1.15.1-s.0
git push origin -d 1.15.1-s.1
git push origin -d 1.15.3-s.0
git push origin -d 1.15.3-s.1
git push origin -d 1.15.4-s.0
git push origin -d 1.15.4-s.1
git push origin -d 1.15.4-s.10
git push origin -d 1.15.4-s.2
git push origin -d 1.15.4-s.3
git push origin -d 1.15.4-s.4
git push origin -d 1.15.4-s.5
git push origin -d 1.15.4-s.6
git push origin -d 1.15.4-s.7
git push origin -d 1.15.4-s.8
git push origin -d 1.15.4-s.9
git push origin -d 1.16.0-s.0
git push origin -d 1.16.0-s.1
git push origin -d 1.16.1-s.0
git push origin -d 1.16.1-s.1
git push origin -d 1.16.2-s.0
git push origin -d 1.16.2-s.1
git push origin -d 1.16.2-s.10
git push origin -d 1.16.2-s.11
git push origin -d 1.16.2-s.12
git push origin -d 1.16.2-s.13
git push origin -d 1.16.2-s.14
git push origin -d 1.16.2-s.15
git push origin -d 1.16.2-s.16
git push origin -d 1.16.2-s.17
git push origin -d 1.16.2-s.18
git push origin -d 1.16.2-s.19
git push origin -d 1.16.2-s.2
git push origin -d 1.16.2-s.20
git push origin -d 1.16.2-s.21
git push origin -d 1.16.2-s.22
git push origin -d 1.16.2-s.3
git push origin -d 1.16.2-s.4
git push origin -d 1.16.2-s.5
git push origin -d 1.16.2-s.6
git push origin -d 1.16.2-s.7
git push origin -d 1.16.2-s.8
git push origin -d 1.16.2-s.9
git push origin -d 1.17.0-s.0
git push origin -d 1.17.0-s.1
git push origin -d 1.17.0-s.2
git push origin -d 1.17.0-s.3
git push origin -d 1.17.0-s.4
git push origin -d 1.17.1-s.0
git push origin -d 1.17.1-s.1
git push origin -d 1.17.1-s.2
git push origin -d 1.17.1-s.3
git push origin -d 1.17.1-s.4
git push origin -d 1.17.1-s.5
git push origin -d 1.17.1-s.6
git push origin -d 1.17.1-s.7
git push origin -d 1.18.0-s.0
git push origin -d 1.18.0-s.1
git push origin -d 1.18.0-s.2
git push origin -d 1.18.1-s.0
git push origin -d 1.18.1-s.1
git push origin -d 1.18.1-s.2
git push origin -d 1.18.1-s.3
git push origin -d 1.18.1-s.4
git push origin -d 1.18.1-s.5
git push origin -d 1.18.1-s.6
git push origin -d 1.18.1-s.7
git push origin -d 1.18.2-s.0
git push origin -d 1.18.2-s.1
git push origin -d 1.18.2-s.2
git push origin -d 1.18.2-s.3
git push origin -d 1.18.2-s.4
git push origin -d 1.18.2-s.5
git push origin -d 1.18.3-s.0
git push origin -d 1.18.3-s.1
git push origin -d 1.18.3-s.2
git push origin -d 1.18.3-s.3
git push origin -d 1.18.4-s.0
git push origin -d 1.18.4-s.1
git push origin -d 1.18.4-s.2
git push origin -d 1.18.4-s.3
git push origin -d 1.18.4-s.4
git push origin -d 1.18.4-s.5
git push origin -d 1.18.4-s.6

View File

@@ -1,4 +1,4 @@
import { APP_PATH } from "@server/lib/consts";
import { APP_PATH } from "./server/lib/consts";
import { defineConfig } from "drizzle-kit";
import path from "path";

View File

@@ -22,7 +22,8 @@ server:
methods: ["GET", "POST", "PUT", "DELETE", "PATCH"]
allowed_headers: ["X-CSRF-Token", "Content-Type"]
credentials: false
{{if .EnableGeoblocking}}maxmind_db_path: "./config/GeoLite2-Country.mmdb"{{end}}
{{if .EnableMaxMind}}maxmind_db_path: "./config/GeoLite2-Country.mmdb"{{end}}
{{if .EnableMaxMind}}maxmind_asn_path: "./config/GeoLite2-ASN.mmdb"{{end}}
{{if .EnableEmail}}
email:
smtp_host: "{{.EmailSMTPHost}}"

View File

@@ -5,7 +5,7 @@ go 1.25.0
require (
github.com/charmbracelet/huh v1.0.0
github.com/charmbracelet/lipgloss v1.1.0
golang.org/x/term v0.42.0
golang.org/x/term v0.43.0
gopkg.in/yaml.v3 v3.0.1
)
@@ -33,6 +33,6 @@ require (
github.com/rivo/uniseg v0.4.7 // indirect
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
golang.org/x/sync v0.15.0 // indirect
golang.org/x/sys v0.43.0 // indirect
golang.org/x/sys v0.44.0 // indirect
golang.org/x/text v0.23.0 // indirect
)

View File

@@ -69,10 +69,10 @@ golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8=
golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI=
golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/term v0.42.0 h1:UiKe+zDFmJobeJ5ggPwOshJIVt6/Ft0rcfrXZDLWAWY=
golang.org/x/term v0.42.0/go.mod h1:Dq/D+snpsbazcBG5+F9Q1n2rXV8Ma+71xEjTRufARgY=
golang.org/x/sys v0.44.0 h1:ildZl3J4uzeKP07r2F++Op7E9B29JRUy+a27EibtBTQ=
golang.org/x/sys v0.44.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/term v0.43.0 h1:S4RLU2sB31O/NCl+zFN9Aru9A/Cq2aqKpTZJ6B+DwT4=
golang.org/x/term v0.43.0/go.mod h1:lrhlHNdQJHO+1qVYiHfFKVuVioJIheAc3fBSMFYEIsk=
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=

View File

@@ -54,7 +54,7 @@ type Config struct {
InstallGerbil bool
TraefikBouncerKey string
DoCrowdsecInstall bool
EnableGeoblocking bool
EnableMaxMind bool
Secret string
IsEnterprise bool
}
@@ -123,11 +123,11 @@ func main() {
fmt.Println("\nConfiguration files created successfully!")
// Download MaxMind database if requested
if config.EnableGeoblocking {
fmt.Println("\n=== Downloading MaxMind Database ===")
// Download MaxMind Country / ASN database if requested
if config.EnableMaxMind {
fmt.Println("\n=== Downloading MaxMind Country and ASN Databases ===")
if err := downloadMaxMindDatabase(); err != nil {
fmt.Printf("Error downloading MaxMind database: %v\n", err)
fmt.Printf("Error downloading MaxMind databases: %v\n", err)
fmt.Println("You can download it manually later if needed.")
}
}
@@ -188,15 +188,15 @@ func main() {
fmt.Println("\n=== MaxMind Database Update ===")
if _, err := os.Stat("config/GeoLite2-Country.mmdb"); err == nil {
fmt.Println("MaxMind GeoLite2 Country database found.")
if readBool("Would you like to update the MaxMind database to the latest version?", false) {
if readBool("Would you like to update the MaxMind databases (Country and ASN) to the latest version?", false) {
if err := downloadMaxMindDatabase(); err != nil {
fmt.Printf("Error updating MaxMind database: %v\n", err)
fmt.Println("You can try updating it manually later if needed.")
}
}
} else {
fmt.Println("MaxMind GeoLite2 Country database not found.")
if readBool("Would you like to download the MaxMind GeoLite2 database for geoblocking functionality?", false) {
fmt.Println("MaxMind GeoLite2 Country and ASN databases not found.")
if readBool("Would you like to download the MaxMind GeoLite2 databases for blocking functionality?", false) {
if err := downloadMaxMindDatabase(); err != nil {
fmt.Printf("Error downloading MaxMind database: %v\n", err)
fmt.Println("You can try downloading it manually later if needed.")
@@ -204,8 +204,10 @@ func main() {
// Now you need to update your config file accordingly to enable geoblocking
fmt.Print("Please remember to update your config/config.yml file to enable geoblocking! \n\n")
// add maxmind_db_path: "./config/GeoLite2-Country.mmdb" under server
fmt.Println("Add the following line under the 'server' section:")
// add maxmind_asn_path: "./config/GeoLite2-ASN.mmdb" under server
fmt.Println("Add the following lines under the 'server' section:")
fmt.Println(" maxmind_db_path: \"./config/GeoLite2-Country.mmdb\"")
fmt.Println(" maxmind_asn_path: \"./config/GeoLite2-ASN.mmdb\"")
}
}
}
@@ -527,7 +529,7 @@ func collectUserInput() Config {
fmt.Println("\n=== Advanced Configuration ===")
config.EnableIPv6 = readBool("Is your server IPv6 capable?", true)
config.EnableGeoblocking = readBool("Do you want to download the MaxMind GeoLite2 database for geoblocking functionality?", true)
config.EnableMaxMind = readBool("Do you want to download the MaxMind GeoLite2 Country and ADN databases for blocking functionality?", true)
if config.DashboardDomain == "" {
fmt.Println("Error: Dashboard Domain name is required")
@@ -780,29 +782,42 @@ func checkPortsAvailable(port int) error {
}
func downloadMaxMindDatabase() error {
fmt.Println("Downloading MaxMind GeoLite2 Country database...")
fmt.Println("Downloading MaxMind GeoLite2 Country and ASN databases...")
// Download the GeoLite2 Country database
// Download the GeoLite2 Country databases
if err := run("curl", "-L", "-o", "GeoLite2-Country.tar.gz",
"https://github.com/GitSquared/node-geolite2-redist/raw/refs/heads/master/redist/GeoLite2-Country.tar.gz"); err != nil {
return fmt.Errorf("failed to download GeoLite2 database: %v", err)
return fmt.Errorf("failed to download GeoLite2 Country database: %v", err)
}
if err := run("curl", "-L", "-o", "GeoLite2-ASN.tar.gz",
"https://github.com/GitSquared/node-geolite2-redist/raw/refs/heads/master/redist/GeoLite2-ASN.tar.gz"); err != nil {
return fmt.Errorf("failed to download GeoLite2 ASN database: %v", err)
}
// Extract the database
// Extract the Country database
if err := run("tar", "-xzf", "GeoLite2-Country.tar.gz"); err != nil {
return fmt.Errorf("failed to extract GeoLite2 database: %v", err)
return fmt.Errorf("failed to extract GeoLite2 Country database: %v", err)
}
if err := run("tar", "-xzf", "GeoLite2-ASN.tar.gz"); err != nil {
return fmt.Errorf("failed to extract GeoLite2 ASN database: %v", err)
}
// Find the .mmdb file and move it to the config directory
if err := run("bash", "-c", "mv GeoLite2-Country_*/GeoLite2-Country.mmdb config/"); err != nil {
return fmt.Errorf("failed to move GeoLite2 database to config directory: %v", err)
return fmt.Errorf("failed to move GeoLite2 Country database to config directory: %v", err)
}
if err := run("bash", "-c", "mv GeoLite2-ASN_*/GeoLite2-ASN.mmdb config/"); err != nil {
return fmt.Errorf("failed to move GeoLite2 ASN database to config directory: %v", err)
}
// Clean up the downloaded files
if err := run("rm", "-rf", "GeoLite2-Country.tar.gz", "GeoLite2-Country_*"); err != nil {
fmt.Printf("Warning: failed to clean up temporary files: %v\n", err)
if err := run("sh", "-c", "rm -rf GeoLite2-Country.tar.gz GeoLite2-Country_*"); err != nil {
fmt.Printf("Warning: failed to clean up temporary country files: %v\n", err)
}
if err := run("sh", "-c", "rm -rf GeoLite2-ASN.tar.gz GeoLite2-ASN_*"); err != nil {
fmt.Printf("Warning: failed to clean up temporary ASN files: %v\n", err)
}
fmt.Println("MaxMind GeoLite2 Country database downloaded successfully!")
fmt.Println("MaxMind GeoLite2 Country and ASN database downloaded successfully!")
return nil
}

View File

@@ -176,6 +176,7 @@
"shareErrorCreateDescription": "Възникна грешка при създаването на връзката за споделяне",
"shareCreateDescription": "Всеки с тази връзка може да получи достъп до ресурса",
"shareTitleOptional": "Заглавие (по избор)",
"sharePathOptional": "Path (optional)",
"expireIn": "Изтече",
"neverExpire": "Никога не изтича",
"shareExpireDescription": "Времето на изтичане е колко дълго връзката ще бъде използваема и ще предоставя достъп до ресурса. След това време, връзката няма да работи и потребителите, които са я използвали, ще загубят достъп до ресурса.",
@@ -208,11 +209,33 @@
"resourcesSearch": "Търсене на ресурси...",
"resourceAdd": "Добавете ресурс",
"resourceErrorDelte": "Грешка при изтриване на ресурс",
"resourcePoliciesTitle": "Manage Resource Policies",
"resourcePoliciesAttachedResourcesColumnTitle": "Attached resources",
"resourcePoliciesAttachedResources": "{count} resource(s)",
"resourcePoliciesAttachedResourcesEmpty": "no resources",
"resourcePoliciesDescription": "Create and manage authentication policies to control access to your resources",
"resourcePoliciesSearch": "Search policies...",
"resourcePoliciesAdd": "Add Policy",
"resourcePoliciesDefaultBadgeText": "Default policy",
"resourcePoliciesCreate": "Create Resource Policy",
"resourcePoliciesCreateDescription": "Follow the steps below to create a new policy",
"resourcePolicyName": "Policy Name",
"resourcePolicyNameDescription": "Give this policy a name to identify it across your resources",
"resourcePolicyNamePlaceholder": "e.g. Internal Access Policy",
"resourcePoliciesSeeAll": "See All Policies",
"resourcePolicyAuthMethodAdd": "Add Authentication Method",
"resourcePolicyOtpEmailAdd": "Add OTP emails",
"resourcePolicyRulesAdd": "Add Rules",
"resourcePolicyAuthMethodsDescription": "Allow access to resources via additional auth methods",
"resourcePolicyUsersRolesDescription": "Configure which users and roles can visit associated resources",
"rulesResourcePolicyDescription": "Configure rules to control access resources associated to this policy",
"authentication": "Удостоверяване",
"protected": "Защита",
"notProtected": "Не защитен",
"resourceMessageRemove": "След като се премахне, ресурсът няма повече да бъде достъпен. Всички цели, свързани с ресурса, също ще бъдат премахнати.",
"resourceQuestionRemove": "Сигурни ли сте, че искате да премахнете ресурса от организацията?",
"resourcePolicyMessageRemove": "Once removed, the resource policy will no longer be accessible. All resources associated with the resource will be unlinked and left without authentication.",
"resourcePolicyQuestionRemove": "Are you sure you want to remove the resource policy from the organization?",
"resourceHTTP": "HTTPS ресурс",
"resourceHTTPDescription": "Прокси заявки чрез HTTPS, използвайки напълно квалифицирано име на домейн.",
"resourceRaw": "Суров TCP/UDP ресурс",
@@ -220,8 +243,9 @@
"resourceRawDescriptionCloud": "Получавайте заявки чрез суров TCP/UDP с използване на портен номер. Изисква се сайтовете да се свързват към отдалечен възел.",
"resourceCreate": "Създайте ресурс",
"resourceCreateDescription": "Следвайте стъпките по-долу, за да създадете нов ресурс",
"resourceCreateGeneralDescription": "Configure the basic resource settings including the name and the type",
"resourceSeeAll": "Вижте всички ресурси",
"resourceInfo": "Информация за ресурса",
"resourceCreateGeneral": "General",
"resourceNameDescription": "Това е дисплейното име на ресурса.",
"siteSelect": "Изберете сайт",
"siteSearch": "Търсене на сайт",
@@ -231,12 +255,15 @@
"noCountryFound": "Не е намерена държава.",
"siteSelectionDescription": "Този сайт ще осигури свързаност до целта.",
"resourceType": "Тип ресурс",
"resourceTypeDescription": "Определете как да се достъпи ресурсът",
"resourceTypeDescription": "This controls the resource protocol and how it will be rendered in the browser. This cant be changed later.",
"resourceDomainDescription": "The resource will be served at this fully qualified domain name.",
"resourceHTTPSSettings": "HTTPS настройки",
"resourceHTTPSSettingsDescription": "Конфигурирайте как ресурсът ще бъде достъпен по HTTPS",
"resourcePortDescription": "The external port on the Pangolin instance or node where the resource will be accessible.",
"domainType": "Тип домейн",
"subdomain": "Субдомейн",
"baseDomain": "Базов домейн",
"configure": "Configure",
"subdomnainDescription": "Поддомейнът, където ресурсът ще бъде достъпен.",
"resourceRawSettings": "TCP/UDP настройки",
"resourceRawSettingsDescription": "Конфигурирайте как ресурсът ще бъде достъпен чрез TCP/UDP",
@@ -253,8 +280,27 @@
"resourceLearnRaw": "Научете как да конфигурирате TCP/UDP ресурси",
"resourceBack": "Назад към ресурсите",
"resourceGoTo": "Отидете към ресурса",
"resourcePolicyDelete": "Delete Resource Policy",
"resourcePolicyDeleteConfirm": "Confirm Delete Resource Policy",
"resourceDelete": "Изтрийте ресурс",
"resourceDeleteConfirm": "Потвърдете изтриване на ресурс",
"labelDelete": "Delete Label",
"labelAdd": "Add Label",
"labelCreateSuccessMessage": "Label Created Successfully",
"labelEditSuccessMessage": "Label Modified Successfully",
"labelNameField": "Label Name",
"labelColorField": "Label Color",
"labelPlaceholder": "Ex: homelab",
"labelCreate": "Create Label",
"createLabelDialogTitle": "Create Label",
"createLabelDialogDescription": "Create a new label that can be attached to this organization",
"labelEdit": "Edit Label",
"editLabelDialogTitle": "Update Label",
"editLabelDialogDescription": "Edit a new label that can be attached to this organization",
"labelDeleteConfirm": "Confirm Delete Label",
"labelErrorDelete": "Failed to delete label",
"labelMessageRemove": "This action is permanent. All sites, resources, and clients tagged with this label will be untagged.",
"labelQuestionRemove": "Are you sure you want to remove the label from the organization?",
"visibility": "Видимост",
"enabled": "Активиран",
"disabled": "Деактивиран",
@@ -265,6 +311,8 @@
"rules": "Правила",
"resourceSettingDescription": "Конфигурирайте настройките на ресурса",
"resourceSetting": "Настройки на {resourceName}",
"resourcePolicySettingDescription": "Configure the settings on the resource policy",
"resourcePolicySetting": "{policyName} Settings",
"alwaysAllow": "Заобикаляне на Ауторизацията",
"alwaysDeny": "Блокиране на Достъпа",
"passToAuth": "Прехвърляне към удостоверяване",
@@ -630,7 +678,7 @@
"createdAt": "Създаден на",
"proxyErrorInvalidHeader": "Невалидна стойност за заглавие на хоста. Използвайте формат на име на домейн, или оставете празно поле за да премахнете персонализирано заглавие на хост.",
"proxyErrorTls": "Невалидно име на TLS сървър. Използвайте формат на име на домейн, или оставете празно за да премахнете името на TLS сървъра.",
"proxyEnableSSL": "Активиране на TLS",
"proxyEnableSSL": "Enable TLS",
"proxyEnableSSLDescription": "Активирайте SSL/TLS криптиране за сигурни HTTPS връзки към целите.",
"target": "Цел",
"configureTarget": "Конфигуриране на цели",
@@ -747,6 +795,16 @@
"rulesNoOne": "Няма правила. Добавете правило чрез формуляра.",
"rulesOrder": "Правилата се оценяват по приоритет в нарастващ ред.",
"rulesSubmit": "Запазване на правилата",
"policyErrorCreate": "Error creating policy",
"policyErrorCreateDescription": "An error occurred when creating the policy",
"policyErrorCreateMessageDescription": "An unexpected error occurred",
"policyErrorUpdate": "Error updating policy",
"policyErrorUpdateDescription": "An error occurred when updating the policy",
"policyErrorUpdateMessageDescription": "An unexpected error occurred",
"policyCreatedSuccess": "Resource policy succesfully created",
"policyUpdatedSuccess": "Resource policy succesfully updated",
"authMethodsSave": "Save auth methods",
"rulesSave": "Save Rules",
"resourceErrorCreate": "Грешка при създаване на ресурс",
"resourceErrorCreateDescription": "Възникна грешка при създаването на ресурса",
"resourceErrorCreateMessage": "Грешка при създаване на ресурс:",
@@ -810,6 +868,16 @@
"pincodeAdd": "Добави ПИН код",
"pincodeRemove": "Премахни ПИН код",
"resourceAuthMethods": "Методи за автентикация",
"resourcePolicyAuthMethodsEmpty": "No authentication method",
"resourcePolicyOtpEmpty": "No one time password",
"resourcePolicyReadOnly": "This policy is Read only",
"resourcePolicyReadOnlyDescription": "This resource policy is shared accross multiple resources, you cannot edit it on this page.",
"resourcePolicyTypeSave": "Save Resource type",
"resourcePolicySelect": "Select resource policy",
"resourcePolicySelectError": "Select a resource policy",
"resourcePolicyNotFound": "Policy not found",
"resourcePolicySearch": "Search policies",
"resourcePolicyRulesEmpty": "No authentication rules",
"resourceAuthMethodsDescriptions": "Позволете достъп до ресурса чрез допълнителни методи за автентикация",
"resourceAuthSettingsSave": "Запазено успешно",
"resourceAuthSettingsSaveDescription": "Настройките за автентикация са запазени успешно",
@@ -845,6 +913,12 @@
"resourcePincodeSetupTitle": "Задай ПИН код",
"resourcePincodeSetupTitleDescription": "Задайте ПИН код, за да защитите този ресурс",
"resourceRoleDescription": "Администраторите винаги могат да имат достъп до този ресурс.",
"resourcePolicySelectTitle": "Resource Access Policy",
"resourcePolicySelectDescription": "Select the resource policy type for authentication",
"resourcePolicyInline": "Inline Resource Policy",
"resourcePolicyInlineDescription": "Access Policy scoped to only this resource",
"resourcePolicyShared": "Shared Resource Policy",
"resourcePolicySharedDescription": "Access Policy shared accross multiple resources",
"resourceUsersRoles": "Контроли за достъп",
"resourceUsersRolesDescription": "Конфигурирайте кои потребители и роли могат да посещават този ресурс",
"resourceUsersRolesSubmit": "Запазване на управлението на достъп.",
@@ -1140,6 +1214,18 @@
"idpErrorConnectingTo": "Имаше проблем със свързването към {name}. Моля, свържете се с вашия администратор.",
"idpErrorNotFound": "Не е намерен идентификационен доставчик",
"inviteInvalid": "Невалидна покана",
"labels": "Labels",
"orgLabelsDescription": "Manage labels in this organization.",
"addLabels": "Add labels",
"siteLabelsTab": "Labels",
"siteLabelsDescription": "Manage labels associated with this site.",
"labelsNotFound": "Labels not found",
"labelSearch": "Search labels",
"accessLabelFilterCount": "{count, plural, one {# label} other {# labels}}",
"labelOverflowCount": "+{count, plural, one {# label} other {# labels}}",
"accessLabelFilterClear": "Clear label filters",
"selectColor": "Select color",
"createNewLabel": "Create new org label \"{label}\"",
"inviteInvalidDescription": "Линкът към поканата е невалиден.",
"inviteErrorWrongUser": "Поканата не е за този потребител",
"inviteErrorUserNotExists": "Потребителят не съществува. Моля, създайте акаунт първо.",
@@ -1374,6 +1460,8 @@
"sidebarResources": "Ресурси",
"sidebarProxyResources": "Публично",
"sidebarClientResources": "Частно",
"sidebarPolicies": "Policies",
"sidebarResourcePolicies": "Resources",
"sidebarAccessControl": "Контрол на достъпа",
"sidebarLogsAndAnalytics": "Дневници и анализи",
"sidebarTeam": "Екип",
@@ -1557,7 +1645,8 @@
"standaloneHcFilterSiteIdFallback": "Сайт {id}",
"standaloneHcFilterResourceIdFallback": "Ресурс {id}",
"blueprints": "Чертежи",
"blueprintsDescription": "Прилагайте декларативни конфигурации и преглеждайте предишни изпълнения",
"blueprintsLog": "Blueprints Log",
"blueprintsDescription": "View past blueprint applications and their results",
"blueprintAdd": "Добави Чертеж",
"blueprintGoBack": "Виж всички Чертежи",
"blueprintCreate": "Създай Чертеж",
@@ -1575,7 +1664,17 @@
"contents": "Съдържание",
"parsedContents": "Парсирано съдържание (само за четене)",
"enableDockerSocket": "Активиране на Docker Чернова",
"enableDockerSocketDescription": "Активиране на Docker Socket маркировка за изтегляне на етикети на чернова. Пътят на гнездото трябва да бъде предоставен на Newt.",
"enableDockerSocketDescription": "Enable Docker Socket label scraping for blueprint labels. Socket path must be provided to the site connector. Read about how this works in <docsLink>the documentation</docsLink>.",
"newtAutoUpdate": "Enable Site Auto-Update",
"newtAutoUpdateDescription": "When enabled, site connectors will automatically update to the latest version when a new release is available.",
"siteAutoUpdate": "Site Auto-Update",
"siteAutoUpdateLabel": "Enable Auto-Update",
"siteAutoUpdateDescription": "Control whether this site's connector automatically downloads the latest version.",
"siteAutoUpdateOrgDefault": "Organization default: {state}",
"siteAutoUpdateOverriding": "Overriding organization setting",
"siteAutoUpdateResetToOrg": "Reset to Organization Default",
"siteAutoUpdateEnabled": "enabled",
"siteAutoUpdateDisabled": "disabled",
"viewDockerContainers": "Преглед на Docker контейнери",
"containersIn": "Контейнери в {siteName}",
"selectContainerDescription": "Изберете контейнер, който да ползвате като име на хост за целта. Натиснете порт, за да ползвате порт",
@@ -1620,6 +1719,7 @@
"certificateStatus": "Сертификат",
"certificateStatusAutoRefreshHint": "Състоянието се опреснява автоматично.",
"loading": "Зареждане",
"loadingEllipsis": "Loading...",
"loadingAnalytics": "Зареждане на анализи",
"restart": "Рестарт",
"domains": "Домейни",
@@ -1846,6 +1946,7 @@
"billingManageLicenseSubscription": "Управлявайте абонамента си за платени самостоятелно хоствани лицензионни ключове",
"billingCurrentKeys": "Текущи ключове",
"billingModifyCurrentPlan": "Промяна на текущия план",
"billingManageLicenseSubscriptionDescription": "Manage your subscription for paid self-hosted license keys and download invoices.",
"billingConfirmUpgrade": "Потвърдете повишаването",
"billingConfirmDowngrade": "Потвърдете понижението",
"billingConfirmUpgradeDescription": "Предстои ви да повишите плана си. Прегледайте новите ограничения и цени по-долу.",
@@ -1943,7 +2044,36 @@
"timeIsInSeconds": "Времето е в секунди",
"requireDeviceApproval": "Изискват одобрение на устройства",
"requireDeviceApprovalDescription": "Потребители с тази роля трябва да имат нови устройства одобрени от администратор преди да могат да се свържат и да имат достъп до ресурси.",
"sshAccess": "SSH достъп",
"sshSettings": "SSH Settings",
"rdpSettings": "RDP Settings",
"vncSettings": "VNC Settings",
"sshServer": "SSH Server",
"rdpServer": "RDP Server",
"vncServer": "VNC Server",
"sshServerDescription": "Set up the authentication method, daemon location, and server destination",
"rdpServerDescription": "Configure the destination and port of the RDP server",
"vncServerDescription": "Configure the destination and port of the VNC server",
"sshServerMode": "Mode",
"sshServerModeStandard": "Standard SSH Server",
"sshServerModePangolin": "Pangolin SSH",
"sshServerModeStandardDescription": "Routes commands over network to an SSH server such as OpenSSH.",
"sshServerModeNative": "Native SSH Server",
"sshServerModeNativeDescription": "Executes commands directly on the host via the Site Connector. No network config required.",
"sshAuthenticationMethod": "Authentication Method",
"sshAuthMethodManual": "Manual Authentication",
"sshAuthMethodManualDescription": "Requires existing host credentials. Bypasses automatic provisioning.",
"sshAuthMethodAutomated": "Automated Provisioning",
"sshAuthMethodAutomatedDescription": "Automatically creates users, groups, and sudo permissions on host.",
"sshAuthDaemonLocation": "Auth Daemon Location",
"sshDaemonLocationSiteDescription": "Executes locally on the machine hosting the site connector.",
"sshDaemonLocationRemote": "On Remote Host",
"sshDaemonLocationRemoteDescription": "Executes on a separate target machine on the same network.",
"sshDaemonDisclaimer": "Ensure your target host is properly configured to run the auth daemon before completing this setup, or provisioning will fail.",
"sshDaemonPort": "Daemon Port",
"sshServerDestination": "Server Destination",
"sshServerDestinationDescription": "Configure the destination and port of the SSH server",
"destination": "Destination",
"bgTargetMultiSiteDisclaimer": "Selecting multiple sites enables resilient routing and failover for high availability.",
"roleAllowSsh": "Разреши SSH",
"roleAllowSshAllow": "Разреши",
"roleAllowSshDisallow": "Забрани",
@@ -1957,7 +2087,7 @@
"sshSudoModeCommandsDescription": "Потребителят може да изпълнява само определени команди с sudo.",
"sshSudo": "Разреши sudo",
"sshSudoCommands": "Sudo команди",
"sshSudoCommandsDescription": "Списък, разделен със запетаи, с команди, които потребителят е позволено да изпълнява с sudo.",
"sshSudoCommandsDescription": "Comma separated list of commands the user is allowed to run with sudo. Absolute paths must be used.",
"sshCreateHomeDir": "Създай начална директория",
"sshUnixGroups": "Unix групи",
"sshUnixGroupsDescription": "Списък, разделен със запетаи, с Unix групи, към които да се добави потребителят на целевия хост.",
@@ -2049,8 +2179,9 @@
"editInternalResourceDialogModeCidr": "CIDR",
"editInternalResourceDialogModeHttp": "HTTP",
"editInternalResourceDialogModeHttps": "HTTPS",
"editInternalResourceDialogModeSsh": "SSH",
"editInternalResourceDialogScheme": "Метод",
"editInternalResourceDialogEnableSsl": "Активирайте TLS",
"editInternalResourceDialogEnableSsl": "Enable TLS",
"editInternalResourceDialogEnableSslDescription": "Активирайте SSL/TLS криптиране за сигурни HTTPS връзки към целта.",
"editInternalResourceDialogDestination": "Дестинация",
"editInternalResourceDialogDestinationHostDescription": "IP адресът или името на хоста на ресурса в мрежата на сайта.",
@@ -2098,9 +2229,10 @@
"createInternalResourceDialogModeCidr": "CIDR",
"createInternalResourceDialogModeHttp": "HTTP",
"createInternalResourceDialogModeHttps": "HTTPS",
"createInternalResourceDialogModeSsh": "SSH",
"scheme": "Метод",
"createInternalResourceDialogScheme": "Метод",
"createInternalResourceDialogEnableSsl": "Активирайте TLS",
"createInternalResourceDialogEnableSsl": "Enable TLS",
"createInternalResourceDialogEnableSslDescription": "Активирайте SSL/TLS криптиране за сигурни HTTPS връзки към целта.",
"createInternalResourceDialogDestination": "Дестинация",
"createInternalResourceDialogDestinationHostDescription": "IP адресът или името на хоста на ресурса в мрежата на сайта.",
@@ -2233,7 +2365,7 @@
"description": "По-надежден и по-нисък поддръжка на Самостоятелно-хостван Панголиин сървър с допълнителни екстри",
"introTitle": "Управлявано Самостоятелно-хостван Панголиин",
"introDescription": "е опция за внедряване, предназначена за хора, които искат простота и допълнителна надеждност, като същевременно запазят данните си частни и самостоятелно-хоствани.",
"introDetail": "С тази опция все още управлявате свой собствен Панголиин възел - вашите тунели, TLS терминатора и трафик остават на вашия сървър. Разликата е, че управлението и мониторингът се обработват чрез нашия облачен панел за контрол, който отключва редица предимства:",
"introDetail": "With this option, you still run your own Pangolin node - your tunnels, TLS termination, and traffic all stay on your server. The difference is that management and monitoring are handled through our cloud dashboard, which unlocks a number of benefits:",
"benefitSimplerOperations": {
"title": "По-прости операции",
"description": "Няма нужда да управлявате свой собствен имейл сървър или да настройвате сложни аларми. Ще получите проверки и предупреждения при прекъсване от самото начало."
@@ -2937,7 +3069,7 @@
"learnMore": "Научете повече.",
"backToHome": "Връщане към началната страница.",
"needToSignInToOrg": "Трябва ли да използвате доставчика на идентичност на организацията си?",
"maintenanceMode": "Режим на поддръжка.",
"maintenanceMode": "Maintenance Page",
"maintenanceModeDescription": "Показване на страницата за поддръжка на посетители.",
"maintenanceModeType": "Тип режим на поддръжка.",
"showMaintenancePage": "Показване на страницата за поддръжка на посетители.",
@@ -2967,6 +3099,7 @@
"maintenanceScreenEstimatedCompletion": "Прогнозно завършване:",
"createInternalResourceDialogDestinationRequired": "Дестинацията е задължителна.",
"available": "Налично",
"disabledResourceDescription": "When disabled, the resource will be inaccessible by everyone.",
"archived": "Архивирано",
"noArchivedDevices": "Не са намерени архивирани устройства.",
"deviceArchived": "Устройството е архивирано.",
@@ -3136,7 +3269,7 @@
"httpDestNamePlaceholder": "Моята HTTP дестинация",
"httpDestUrlLabel": "Дестинация URL",
"httpDestUrlErrorHttpRequired": "URL адресът трябва да използва http или https",
"httpDestUrlErrorHttpsRequired": "HTTPS е необходимо за облачни инсталации",
"httpDestUrlErrorHttpsRequired": "SSL е необходимо за облачни инсталации",
"httpDestUrlErrorInvalid": "Въведете валиден URL (напр. https://example.com/webhook)",
"httpDestAuthTitle": "Удостоверяване",
"httpDestAuthDescription": "Изберете как заявленията ви се удостоверяват.",
@@ -3296,5 +3429,6 @@
"memberPortalResourceDisabled": "Ресурсът е деактивиран",
"memberPortalShowingResources": "Показва {start}-{end} от {total} ресурси",
"memberPortalPrevious": "Предишен",
"memberPortalNext": "Следващ"
"memberPortalNext": "Следващ",
"httpSettings": "HTTP Settings"
}

View File

@@ -176,6 +176,7 @@
"shareErrorCreateDescription": "Při vytváření odkazu došlo k chybě",
"shareCreateDescription": "Kdokoliv s tímto odkazem může přistupovat ke zdroji",
"shareTitleOptional": "Název (volitelné)",
"sharePathOptional": "Path (optional)",
"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.",
@@ -208,11 +209,33 @@
"resourcesSearch": "Prohledat zdroje...",
"resourceAdd": "Přidat zdroj",
"resourceErrorDelte": "Chyba při odstraňování zdroje",
"resourcePoliciesTitle": "Manage Resource Policies",
"resourcePoliciesAttachedResourcesColumnTitle": "Attached resources",
"resourcePoliciesAttachedResources": "{count} resource(s)",
"resourcePoliciesAttachedResourcesEmpty": "no resources",
"resourcePoliciesDescription": "Create and manage authentication policies to control access to your resources",
"resourcePoliciesSearch": "Search policies...",
"resourcePoliciesAdd": "Add Policy",
"resourcePoliciesDefaultBadgeText": "Default policy",
"resourcePoliciesCreate": "Create Resource Policy",
"resourcePoliciesCreateDescription": "Follow the steps below to create a new policy",
"resourcePolicyName": "Policy Name",
"resourcePolicyNameDescription": "Give this policy a name to identify it across your resources",
"resourcePolicyNamePlaceholder": "e.g. Internal Access Policy",
"resourcePoliciesSeeAll": "See All Policies",
"resourcePolicyAuthMethodAdd": "Add Authentication Method",
"resourcePolicyOtpEmailAdd": "Add OTP emails",
"resourcePolicyRulesAdd": "Add Rules",
"resourcePolicyAuthMethodsDescription": "Allow access to resources via additional auth methods",
"resourcePolicyUsersRolesDescription": "Configure which users and roles can visit associated resources",
"rulesResourcePolicyDescription": "Configure rules to control access resources associated to this policy",
"authentication": "Autentifikace",
"protected": "Chráněno",
"notProtected": "Nechráněno",
"resourceMessageRemove": "Jakmile zdroj odstraníte, nebude dostupný. Všechny související služby a cíle budou také odstraněny.",
"resourceQuestionRemove": "Jste si jisti, že chcete odstranit zdroj z organizace?",
"resourcePolicyMessageRemove": "Once removed, the resource policy will no longer be accessible. All resources associated with the resource will be unlinked and left without authentication.",
"resourcePolicyQuestionRemove": "Are you sure you want to remove the resource policy from the organization?",
"resourceHTTP": "Zdroj HTTPS",
"resourceHTTPDescription": "Proxy požadavky přes HTTPS pomocí plně kvalifikovaného názvu domény.",
"resourceRaw": "Surový TCP/UDP zdroj",
@@ -220,8 +243,9 @@
"resourceRawDescriptionCloud": "Proxy požadavky na syrové TCP/UDP pomocí čísla portu. Vyžaduje připojení stránek ke vzdálenému uzlu.",
"resourceCreate": "Vytvořit zdroj",
"resourceCreateDescription": "Postupujte podle níže uvedených kroků, abyste vytvořili a připojili nový zdroj",
"resourceCreateGeneralDescription": "Configure the basic resource settings including the name and the type",
"resourceSeeAll": "Zobrazit všechny zdroje",
"resourceInfo": "Informace o zdroji",
"resourceCreateGeneral": "General",
"resourceNameDescription": "Toto je zobrazovaný název zdroje.",
"siteSelect": "Vybrat lokalitu",
"siteSearch": "Hledat lokalitu",
@@ -231,12 +255,15 @@
"noCountryFound": "Nebyla nalezena žádná země.",
"siteSelectionDescription": "Tato lokalita poskytne připojení k cíli.",
"resourceType": "Typ zdroje",
"resourceTypeDescription": "Určete, jak přistupovat ke zdroji",
"resourceTypeDescription": "This controls the resource protocol and how it will be rendered in the browser. This cant be changed later.",
"resourceDomainDescription": "The resource will be served at this fully qualified domain name.",
"resourceHTTPSSettings": "Nastavení HTTPS",
"resourceHTTPSSettingsDescription": "Nakonfigurujte, jak bude dokument přístupný přes HTTPS",
"resourcePortDescription": "The external port on the Pangolin instance or node where the resource will be accessible.",
"domainType": "Typ domény",
"subdomain": "Subdoména",
"baseDomain": "Základní doména",
"configure": "Configure",
"subdomnainDescription": "Subdoména, kde bude zdroj přístupný.",
"resourceRawSettings": "Nastavení TCP/UDP",
"resourceRawSettingsDescription": "Nakonfigurujte, jak bude dokument přístupný přes TCP/UDP",
@@ -253,8 +280,27 @@
"resourceLearnRaw": "Naučte se konfigurovat zdroje TCP/UDP",
"resourceBack": "Zpět na zdroje",
"resourceGoTo": "Přejít na dokument",
"resourcePolicyDelete": "Delete Resource Policy",
"resourcePolicyDeleteConfirm": "Confirm Delete Resource Policy",
"resourceDelete": "Odstranit dokument",
"resourceDeleteConfirm": "Potvrdit odstranění dokumentu",
"labelDelete": "Delete Label",
"labelAdd": "Add Label",
"labelCreateSuccessMessage": "Label Created Successfully",
"labelEditSuccessMessage": "Label Modified Successfully",
"labelNameField": "Label Name",
"labelColorField": "Label Color",
"labelPlaceholder": "Ex: homelab",
"labelCreate": "Create Label",
"createLabelDialogTitle": "Create Label",
"createLabelDialogDescription": "Create a new label that can be attached to this organization",
"labelEdit": "Edit Label",
"editLabelDialogTitle": "Update Label",
"editLabelDialogDescription": "Edit a new label that can be attached to this organization",
"labelDeleteConfirm": "Confirm Delete Label",
"labelErrorDelete": "Failed to delete label",
"labelMessageRemove": "This action is permanent. All sites, resources, and clients tagged with this label will be untagged.",
"labelQuestionRemove": "Are you sure you want to remove the label from the organization?",
"visibility": "Viditelnost",
"enabled": "Povoleno",
"disabled": "Zakázáno",
@@ -265,6 +311,8 @@
"rules": "Pravidla",
"resourceSettingDescription": "Konfigurace nastavení na zdroji",
"resourceSetting": "Nastavení {resourceName}",
"resourcePolicySettingDescription": "Configure the settings on the resource policy",
"resourcePolicySetting": "{policyName} Settings",
"alwaysAllow": "Obejít Auth",
"alwaysDeny": "Blokovat přístup",
"passToAuth": "Předat k ověření",
@@ -630,7 +678,7 @@
"createdAt": "Vytvořeno v",
"proxyErrorInvalidHeader": "Neplatná hodnota hlavičky hostitele. Použijte formát názvu domény, nebo uložte prázdné pro zrušení vlastního hlavičky hostitele.",
"proxyErrorTls": "Neplatné jméno TLS serveru. Použijte formát doménového jména nebo uložte prázdné pro odstranění názvu TLS serveru.",
"proxyEnableSSL": "Povolit TLS",
"proxyEnableSSL": "Enable TLS",
"proxyEnableSSLDescription": "Povolit šifrování SSL/TLS pro zabezpečená připojení HTTPS k cílům.",
"target": "Target",
"configureTarget": "Konfigurace cílů",
@@ -747,6 +795,16 @@
"rulesNoOne": "Žádná pravidla. Přidejte pravidlo pomocí formuláře.",
"rulesOrder": "Pravidla jsou hodnocena podle priority vzestupně.",
"rulesSubmit": "Uložit pravidla",
"policyErrorCreate": "Error creating policy",
"policyErrorCreateDescription": "An error occurred when creating the policy",
"policyErrorCreateMessageDescription": "An unexpected error occurred",
"policyErrorUpdate": "Error updating policy",
"policyErrorUpdateDescription": "An error occurred when updating the policy",
"policyErrorUpdateMessageDescription": "An unexpected error occurred",
"policyCreatedSuccess": "Resource policy succesfully created",
"policyUpdatedSuccess": "Resource policy succesfully updated",
"authMethodsSave": "Save auth methods",
"rulesSave": "Save Rules",
"resourceErrorCreate": "Chyba při vytváření zdroje",
"resourceErrorCreateDescription": "Při vytváření zdroje došlo k chybě",
"resourceErrorCreateMessage": "Chyba při vytváření zdroje:",
@@ -810,6 +868,16 @@
"pincodeAdd": "Přidat PIN kód",
"pincodeRemove": "Odstranit PIN kód",
"resourceAuthMethods": "Metody ověřování",
"resourcePolicyAuthMethodsEmpty": "No authentication method",
"resourcePolicyOtpEmpty": "No one time password",
"resourcePolicyReadOnly": "This policy is Read only",
"resourcePolicyReadOnlyDescription": "This resource policy is shared accross multiple resources, you cannot edit it on this page.",
"resourcePolicyTypeSave": "Save Resource type",
"resourcePolicySelect": "Select resource policy",
"resourcePolicySelectError": "Select a resource policy",
"resourcePolicyNotFound": "Policy not found",
"resourcePolicySearch": "Search policies",
"resourcePolicyRulesEmpty": "No authentication rules",
"resourceAuthMethodsDescriptions": "Povolit přístup ke zdroji pomocí dodatečných metod autorizace",
"resourceAuthSettingsSave": "Úspěšně uloženo",
"resourceAuthSettingsSaveDescription": "Nastavení ověřování bylo uloženo",
@@ -845,6 +913,12 @@
"resourcePincodeSetupTitle": "Nastavit anonymní kód",
"resourcePincodeSetupTitleDescription": "Nastavit pincode pro ochranu tohoto zdroje",
"resourceRoleDescription": "Administrátoři mají vždy přístup k tomuto zdroji.",
"resourcePolicySelectTitle": "Resource Access Policy",
"resourcePolicySelectDescription": "Select the resource policy type for authentication",
"resourcePolicyInline": "Inline Resource Policy",
"resourcePolicyInlineDescription": "Access Policy scoped to only this resource",
"resourcePolicyShared": "Shared Resource Policy",
"resourcePolicySharedDescription": "Access Policy shared accross multiple resources",
"resourceUsersRoles": "Kontrola přístupu",
"resourceUsersRolesDescription": "Nastavení, kteří uživatelé a role mohou navštívit tento zdroj",
"resourceUsersRolesSubmit": "Uložit přístupové řízení",
@@ -1140,6 +1214,18 @@
"idpErrorConnectingTo": "Při připojování k {name}došlo k chybě. Obraťte se na správce.",
"idpErrorNotFound": "IdP nenalezen",
"inviteInvalid": "Neplatná pozvánka",
"labels": "Labels",
"orgLabelsDescription": "Manage labels in this organization.",
"addLabels": "Add labels",
"siteLabelsTab": "Labels",
"siteLabelsDescription": "Manage labels associated with this site.",
"labelsNotFound": "Labels not found",
"labelSearch": "Search labels",
"accessLabelFilterCount": "{count, plural, one {# label} other {# labels}}",
"labelOverflowCount": "+{count, plural, one {# label} other {# labels}}",
"accessLabelFilterClear": "Clear label filters",
"selectColor": "Select color",
"createNewLabel": "Create new org label \"{label}\"",
"inviteInvalidDescription": "Odkaz pro pozvání je neplatný.",
"inviteErrorWrongUser": "Pozvat není pro tohoto uživatele",
"inviteErrorUserNotExists": "Uživatel neexistuje. Nejprve si vytvořte účet.",
@@ -1374,6 +1460,8 @@
"sidebarResources": "Zdroje",
"sidebarProxyResources": "Veřejnost",
"sidebarClientResources": "Soukromé",
"sidebarPolicies": "Policies",
"sidebarResourcePolicies": "Resources",
"sidebarAccessControl": "Kontrola přístupu",
"sidebarLogsAndAnalytics": "Logy & Analytika",
"sidebarTeam": "Tým",
@@ -1557,7 +1645,8 @@
"standaloneHcFilterSiteIdFallback": "Stránka {id}",
"standaloneHcFilterResourceIdFallback": "Zdroj {id}",
"blueprints": "Plány",
"blueprintsDescription": "Použít deklarativní konfigurace a zobrazit předchozí běhy",
"blueprintsLog": "Blueprints Log",
"blueprintsDescription": "View past blueprint applications and their results",
"blueprintAdd": "Přidat plán",
"blueprintGoBack": "Zobrazit všechny plány",
"blueprintCreate": "Vytvořit plán",
@@ -1575,7 +1664,17 @@
"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.",
"enableDockerSocketDescription": "Enable Docker Socket label scraping for blueprint labels. Socket path must be provided to the site connector. Read about how this works in <docsLink>the documentation</docsLink>.",
"newtAutoUpdate": "Enable Site Auto-Update",
"newtAutoUpdateDescription": "When enabled, site connectors will automatically update to the latest version when a new release is available.",
"siteAutoUpdate": "Site Auto-Update",
"siteAutoUpdateLabel": "Enable Auto-Update",
"siteAutoUpdateDescription": "Control whether this site's connector automatically downloads the latest version.",
"siteAutoUpdateOrgDefault": "Organization default: {state}",
"siteAutoUpdateOverriding": "Overriding organization setting",
"siteAutoUpdateResetToOrg": "Reset to Organization Default",
"siteAutoUpdateEnabled": "enabled",
"siteAutoUpdateDisabled": "disabled",
"viewDockerContainers": "Zobrazit kontejnery Dockeru",
"containersIn": "Kontejnery v {siteName}",
"selectContainerDescription": "Vyberte jakýkoli kontejner pro použití jako název hostitele pro tento cíl. Klikněte na port pro použití portu.",
@@ -1620,6 +1719,7 @@
"certificateStatus": "Certifikát",
"certificateStatusAutoRefreshHint": "Stav se automaticky obnovuje.",
"loading": "Načítání",
"loadingEllipsis": "Loading...",
"loadingAnalytics": "Načítání analytiky",
"restart": "Restartovat",
"domains": "Domény",
@@ -1846,6 +1946,7 @@
"billingManageLicenseSubscription": "Spravujte své předplatné za placené samohostované licenční klíče",
"billingCurrentKeys": "Aktuální klíče",
"billingModifyCurrentPlan": "Upravit aktuální tarif",
"billingManageLicenseSubscriptionDescription": "Manage your subscription for paid self-hosted license keys and download invoices.",
"billingConfirmUpgrade": "Potvrdit aktualizaci",
"billingConfirmDowngrade": "Potvrdit downgrade",
"billingConfirmUpgradeDescription": "Chystáte se povýšit svůj tarif. Přečtěte si nové limity a ceny.",
@@ -1943,7 +2044,36 @@
"timeIsInSeconds": "Čas je v sekundách",
"requireDeviceApproval": "Vyžadovat schválení zařízení",
"requireDeviceApprovalDescription": "Uživatelé s touto rolí potřebují nová zařízení schválená správcem, než se mohou připojit a přistupovat ke zdrojům.",
"sshAccess": "SSH přístup",
"sshSettings": "SSH Settings",
"rdpSettings": "RDP Settings",
"vncSettings": "VNC Settings",
"sshServer": "SSH Server",
"rdpServer": "RDP Server",
"vncServer": "VNC Server",
"sshServerDescription": "Set up the authentication method, daemon location, and server destination",
"rdpServerDescription": "Configure the destination and port of the RDP server",
"vncServerDescription": "Configure the destination and port of the VNC server",
"sshServerMode": "Mode",
"sshServerModeStandard": "Standard SSH Server",
"sshServerModePangolin": "Pangolin SSH",
"sshServerModeStandardDescription": "Routes commands over network to an SSH server such as OpenSSH.",
"sshServerModeNative": "Native SSH Server",
"sshServerModeNativeDescription": "Executes commands directly on the host via the Site Connector. No network config required.",
"sshAuthenticationMethod": "Authentication Method",
"sshAuthMethodManual": "Manual Authentication",
"sshAuthMethodManualDescription": "Requires existing host credentials. Bypasses automatic provisioning.",
"sshAuthMethodAutomated": "Automated Provisioning",
"sshAuthMethodAutomatedDescription": "Automatically creates users, groups, and sudo permissions on host.",
"sshAuthDaemonLocation": "Auth Daemon Location",
"sshDaemonLocationSiteDescription": "Executes locally on the machine hosting the site connector.",
"sshDaemonLocationRemote": "On Remote Host",
"sshDaemonLocationRemoteDescription": "Executes on a separate target machine on the same network.",
"sshDaemonDisclaimer": "Ensure your target host is properly configured to run the auth daemon before completing this setup, or provisioning will fail.",
"sshDaemonPort": "Daemon Port",
"sshServerDestination": "Server Destination",
"sshServerDestinationDescription": "Configure the destination and port of the SSH server",
"destination": "Destination",
"bgTargetMultiSiteDisclaimer": "Selecting multiple sites enables resilient routing and failover for high availability.",
"roleAllowSsh": "Povolit SSH",
"roleAllowSshAllow": "Povolit",
"roleAllowSshDisallow": "Zakázat",
@@ -1957,7 +2087,7 @@
"sshSudoModeCommandsDescription": "Uživatel může spustit pouze zadané příkazy s sudo.",
"sshSudo": "Povolit sudo",
"sshSudoCommands": "Sudo příkazy",
"sshSudoCommandsDescription": "Čárkami oddělený seznam příkazů, které může uživatel spouštět s sudo.",
"sshSudoCommandsDescription": "Comma separated list of commands the user is allowed to run with sudo. Absolute paths must be used.",
"sshCreateHomeDir": "Vytvořit domovský adresář",
"sshUnixGroups": "Unixové skupiny",
"sshUnixGroupsDescription": "Čárkou oddělené skupiny Unix přidají uživatele do cílového hostitele.",
@@ -2049,8 +2179,9 @@
"editInternalResourceDialogModeCidr": "CIDR",
"editInternalResourceDialogModeHttp": "HTTP",
"editInternalResourceDialogModeHttps": "HTTPS",
"editInternalResourceDialogModeSsh": "SSH",
"editInternalResourceDialogScheme": "Schéma",
"editInternalResourceDialogEnableSsl": "Povolit SSL",
"editInternalResourceDialogEnableSsl": "Enable TLS",
"editInternalResourceDialogEnableSslDescription": "Povolit šifrování SSL/TLS pro zabezpečené HTTPS připojení k cíli.",
"editInternalResourceDialogDestination": "Místo určení",
"editInternalResourceDialogDestinationHostDescription": "IP adresa nebo název hostitele zdroje v síti webu.",
@@ -2098,9 +2229,10 @@
"createInternalResourceDialogModeCidr": "CIDR",
"createInternalResourceDialogModeHttp": "HTTP",
"createInternalResourceDialogModeHttps": "HTTPS",
"createInternalResourceDialogModeSsh": "SSH",
"scheme": "Schéma",
"createInternalResourceDialogScheme": "Schéma",
"createInternalResourceDialogEnableSsl": "Povolit SSL",
"createInternalResourceDialogEnableSsl": "Enable TLS",
"createInternalResourceDialogEnableSslDescription": "Povolit šifrování SSL/TLS pro zabezpečené HTTPS připojení k cíli.",
"createInternalResourceDialogDestination": "Místo určení",
"createInternalResourceDialogDestinationHostDescription": "IP adresa nebo název hostitele zdroje v síti webu.",
@@ -2233,7 +2365,7 @@
"description": "Spolehlivější a nízko udržovaný Pangolinův server s dalšími zvony a bičkami",
"introTitle": "Spravovaný Pangolin",
"introDescription": "je možnost nasazení určená pro lidi, kteří chtějí jednoduchost a spolehlivost při zachování soukromých a samoobslužných dat.",
"introDetail": "Pomocí této volby stále provozujete vlastní uzel Pangolin - tunely, SSL terminály a provoz všech pobytů na vašem serveru. Rozdíl spočívá v tom, že řízení a monitorování se řeší prostřednictvím našeho cloudového panelu, který odemkne řadu výhod:",
"introDetail": "With this option, you still run your own Pangolin node - your tunnels, TLS termination, and traffic all stay on your server. The difference is that management and monitoring are handled through our cloud dashboard, which unlocks a number of benefits:",
"benefitSimplerOperations": {
"title": "Jednoduchý provoz",
"description": "Není třeba spouštět svůj vlastní poštovní server nebo nastavit komplexní upozornění. Ze schránky dostanete upozornění na zdravotní kontrolu a výpadek."
@@ -2937,7 +3069,7 @@
"learnMore": "Zjistit více",
"backToHome": "Zpět na domovskou stránku",
"needToSignInToOrg": "Potřebujete použít identitního poskytovatele vaší organizace?",
"maintenanceMode": "Režim údržby",
"maintenanceMode": "Maintenance Page",
"maintenanceModeDescription": "Zobrazit stránku údržby návštěvníkům",
"maintenanceModeType": "Typ režimu údržby",
"showMaintenancePage": "Zobrazit stránku údržby návštěvníkům",
@@ -2967,6 +3099,7 @@
"maintenanceScreenEstimatedCompletion": "Odhadované dokončení:",
"createInternalResourceDialogDestinationRequired": "Cíl je povinný",
"available": "Dostupné",
"disabledResourceDescription": "When disabled, the resource will be inaccessible by everyone.",
"archived": "Archivováno",
"noArchivedDevices": "Nebyla nalezena žádná archivovaná zařízení",
"deviceArchived": "Zařízení archivováno",
@@ -3296,5 +3429,6 @@
"memberPortalResourceDisabled": "Zdroj je zakázán",
"memberPortalShowingResources": "Zobrazeny {start}-{end} z {total} zdrojů",
"memberPortalPrevious": "Předchozí",
"memberPortalNext": "Následující"
"memberPortalNext": "Následující",
"httpSettings": "HTTP Settings"
}

View File

@@ -176,6 +176,7 @@
"shareErrorCreateDescription": "Beim Erstellen des Freigabelinks ist ein Fehler aufgetreten",
"shareCreateDescription": "Jeder mit diesem Link kann auf die Ressource zugreifen",
"shareTitleOptional": "Titel (optional)",
"sharePathOptional": "Path (optional)",
"expireIn": "Läuft ab in",
"neverExpire": "Läuft nie ab",
"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.",
@@ -208,11 +209,33 @@
"resourcesSearch": "Suche Ressourcen...",
"resourceAdd": "Ressource hinzufügen",
"resourceErrorDelte": "Fehler beim Löschen der Ressource",
"resourcePoliciesTitle": "Manage Resource Policies",
"resourcePoliciesAttachedResourcesColumnTitle": "Attached resources",
"resourcePoliciesAttachedResources": "{count} resource(s)",
"resourcePoliciesAttachedResourcesEmpty": "no resources",
"resourcePoliciesDescription": "Create and manage authentication policies to control access to your resources",
"resourcePoliciesSearch": "Search policies...",
"resourcePoliciesAdd": "Add Policy",
"resourcePoliciesDefaultBadgeText": "Default policy",
"resourcePoliciesCreate": "Create Resource Policy",
"resourcePoliciesCreateDescription": "Follow the steps below to create a new policy",
"resourcePolicyName": "Policy Name",
"resourcePolicyNameDescription": "Give this policy a name to identify it across your resources",
"resourcePolicyNamePlaceholder": "e.g. Internal Access Policy",
"resourcePoliciesSeeAll": "See All Policies",
"resourcePolicyAuthMethodAdd": "Add Authentication Method",
"resourcePolicyOtpEmailAdd": "Add OTP emails",
"resourcePolicyRulesAdd": "Add Rules",
"resourcePolicyAuthMethodsDescription": "Allow access to resources via additional auth methods",
"resourcePolicyUsersRolesDescription": "Configure which users and roles can visit associated resources",
"rulesResourcePolicyDescription": "Configure rules to control access resources associated to this policy",
"authentication": "Authentifizierung",
"protected": "Geschützt",
"notProtected": "Nicht geschützt",
"resourceMessageRemove": "Einmal entfernt, wird die Ressource nicht mehr zugänglich sein. Alle mit der Ressource verbundenen Ziele werden ebenfalls entfernt.",
"resourceQuestionRemove": "Sind Sie sicher, dass Sie die Ressource aus der Organisation entfernen möchten?",
"resourcePolicyMessageRemove": "Once removed, the resource policy will no longer be accessible. All resources associated with the resource will be unlinked and left without authentication.",
"resourcePolicyQuestionRemove": "Are you sure you want to remove the resource policy from the organization?",
"resourceHTTP": "HTTPS-Ressource",
"resourceHTTPDescription": "Proxy-Anfragen über HTTPS mit einem voll qualifizierten Domain-Namen.",
"resourceRaw": "Direkte TCP/UDP Ressource (raw)",
@@ -220,8 +243,9 @@
"resourceRawDescriptionCloud": "Proxy-Anfragen über rohe TCP/UDP mit Portnummer. Benötigt Sites, um sich mit einem entfernten Knoten zu verbinden.",
"resourceCreate": "Ressource erstellen",
"resourceCreateDescription": "Folgen Sie den Schritten unten, um eine neue Ressource zu erstellen",
"resourceCreateGeneralDescription": "Configure the basic resource settings including the name and the type",
"resourceSeeAll": "Alle Ressourcen anzeigen",
"resourceInfo": "Ressourcen-Informationen",
"resourceCreateGeneral": "General",
"resourceNameDescription": "Dies ist der Anzeigename für die Ressource.",
"siteSelect": "Standort auswählen",
"siteSearch": "Standorte durchsuchen",
@@ -231,12 +255,15 @@
"noCountryFound": "Kein Land gefunden.",
"siteSelectionDescription": "Dieser Standort wird die Verbindung zum Ziel herstellen.",
"resourceType": "Ressourcentyp",
"resourceTypeDescription": "Legen Sie fest, wie Sie auf die Ressource zugreifen",
"resourceTypeDescription": "This controls the resource protocol and how it will be rendered in the browser. This cant be changed later.",
"resourceDomainDescription": "The resource will be served at this fully qualified domain name.",
"resourceHTTPSSettings": "HTTPS-Einstellungen",
"resourceHTTPSSettingsDescription": "Konfigurieren Sie den Zugriff auf die Ressource über HTTPS",
"resourcePortDescription": "The external port on the Pangolin instance or node where the resource will be accessible.",
"domainType": "Domain-Typ",
"subdomain": "Subdomain",
"baseDomain": "Basis-Domain",
"configure": "Configure",
"subdomnainDescription": "Die Subdomäne, auf die die Ressource zugegriffen werden soll.",
"resourceRawSettings": "TCP/UDP Einstellungen",
"resourceRawSettingsDescription": "Konfigurieren, wie auf die Ressource über TCP/UDP zugegriffen wird",
@@ -253,8 +280,27 @@
"resourceLearnRaw": "Lernen Sie, wie Sie TCP/UDP Ressourcen konfigurieren",
"resourceBack": "Zurück zu den Ressourcen",
"resourceGoTo": "Zu Ressource gehen",
"resourcePolicyDelete": "Delete Resource Policy",
"resourcePolicyDeleteConfirm": "Confirm Delete Resource Policy",
"resourceDelete": "Ressource löschen",
"resourceDeleteConfirm": "Ressource löschen bestätigen",
"labelDelete": "Delete Label",
"labelAdd": "Add Label",
"labelCreateSuccessMessage": "Label Created Successfully",
"labelEditSuccessMessage": "Label Modified Successfully",
"labelNameField": "Label Name",
"labelColorField": "Label Color",
"labelPlaceholder": "Ex: homelab",
"labelCreate": "Create Label",
"createLabelDialogTitle": "Create Label",
"createLabelDialogDescription": "Create a new label that can be attached to this organization",
"labelEdit": "Edit Label",
"editLabelDialogTitle": "Update Label",
"editLabelDialogDescription": "Edit a new label that can be attached to this organization",
"labelDeleteConfirm": "Confirm Delete Label",
"labelErrorDelete": "Failed to delete label",
"labelMessageRemove": "This action is permanent. All sites, resources, and clients tagged with this label will be untagged.",
"labelQuestionRemove": "Are you sure you want to remove the label from the organization?",
"visibility": "Sichtbarkeit",
"enabled": "Aktiviert",
"disabled": "Deaktiviert",
@@ -265,6 +311,8 @@
"rules": "Regeln",
"resourceSettingDescription": "Einstellungen für die Ressource konfigurieren",
"resourceSetting": "{resourceName} Einstellungen",
"resourcePolicySettingDescription": "Configure the settings on the resource policy",
"resourcePolicySetting": "{policyName} Settings",
"alwaysAllow": "Authentifizierung umgehen",
"alwaysDeny": "Zugriff blockieren",
"passToAuth": "Weiterleiten zur Authentifizierung",
@@ -630,7 +678,7 @@
"createdAt": "Erstellt am",
"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": "TLS aktivieren",
"proxyEnableSSL": "Enable TLS",
"proxyEnableSSLDescription": "Aktiviere SSL/TLS-Verschlüsselung für sichere HTTPS-Verbindungen zu den Zielen.",
"target": "Ziel",
"configureTarget": "Ziele konfigurieren",
@@ -747,6 +795,16 @@
"rulesNoOne": "Keine Regeln. Fügen Sie eine Regel über das Formular hinzu.",
"rulesOrder": "Regeln werden nach aufsteigender Priorität ausgewertet.",
"rulesSubmit": "Regeln speichern",
"policyErrorCreate": "Error creating policy",
"policyErrorCreateDescription": "An error occurred when creating the policy",
"policyErrorCreateMessageDescription": "An unexpected error occurred",
"policyErrorUpdate": "Error updating policy",
"policyErrorUpdateDescription": "An error occurred when updating the policy",
"policyErrorUpdateMessageDescription": "An unexpected error occurred",
"policyCreatedSuccess": "Resource policy succesfully created",
"policyUpdatedSuccess": "Resource policy succesfully updated",
"authMethodsSave": "Save auth methods",
"rulesSave": "Save Rules",
"resourceErrorCreate": "Fehler beim Erstellen der Ressource",
"resourceErrorCreateDescription": "Beim Erstellen der Ressource ist ein Fehler aufgetreten",
"resourceErrorCreateMessage": "Fehler beim Erstellen der Ressource:",
@@ -810,6 +868,16 @@
"pincodeAdd": "PIN-Code hinzufügen",
"pincodeRemove": "PIN-Code entfernen",
"resourceAuthMethods": "Authentifizierungsmethoden",
"resourcePolicyAuthMethodsEmpty": "No authentication method",
"resourcePolicyOtpEmpty": "No one time password",
"resourcePolicyReadOnly": "This policy is Read only",
"resourcePolicyReadOnlyDescription": "This resource policy is shared accross multiple resources, you cannot edit it on this page.",
"resourcePolicyTypeSave": "Save Resource type",
"resourcePolicySelect": "Select resource policy",
"resourcePolicySelectError": "Select a resource policy",
"resourcePolicyNotFound": "Policy not found",
"resourcePolicySearch": "Search policies",
"resourcePolicyRulesEmpty": "No authentication rules",
"resourceAuthMethodsDescriptions": "Ermöglichen Sie den Zugriff auf die Ressource über zusätzliche Authentifizierungsmethoden",
"resourceAuthSettingsSave": "Erfolgreich gespeichert",
"resourceAuthSettingsSaveDescription": "Authentifizierungseinstellungen wurden gespeichert",
@@ -845,6 +913,12 @@
"resourcePincodeSetupTitle": "PIN-Code festlegen",
"resourcePincodeSetupTitleDescription": "Legen Sie einen PIN-Code fest, um diese Ressource zu schützen",
"resourceRoleDescription": "Administratoren haben immer Zugriff auf diese Ressource.",
"resourcePolicySelectTitle": "Resource Access Policy",
"resourcePolicySelectDescription": "Select the resource policy type for authentication",
"resourcePolicyInline": "Inline Resource Policy",
"resourcePolicyInlineDescription": "Access Policy scoped to only this resource",
"resourcePolicyShared": "Shared Resource Policy",
"resourcePolicySharedDescription": "Access Policy shared accross multiple resources",
"resourceUsersRoles": "Zugriffskontrolle",
"resourceUsersRolesDescription": "Konfigurieren Sie, welche Benutzer und Rollen diese Ressource besuchen können",
"resourceUsersRolesSubmit": "Zugriffskontrollen speichern",
@@ -1140,6 +1214,18 @@
"idpErrorConnectingTo": "Es gab ein Problem bei der Verbindung zu {name}. Bitte kontaktieren Sie Ihren Administrator.",
"idpErrorNotFound": "IdP nicht gefunden",
"inviteInvalid": "Ungültige Einladung",
"labels": "Labels",
"orgLabelsDescription": "Manage labels in this organization.",
"addLabels": "Add labels",
"siteLabelsTab": "Labels",
"siteLabelsDescription": "Manage labels associated with this site.",
"labelsNotFound": "Labels not found",
"labelSearch": "Search labels",
"accessLabelFilterCount": "{count, plural, one {# label} other {# labels}}",
"labelOverflowCount": "+{count, plural, one {# label} other {# labels}}",
"accessLabelFilterClear": "Clear label filters",
"selectColor": "Select color",
"createNewLabel": "Create new org label \"{label}\"",
"inviteInvalidDescription": "Der Einladungslink ist ungültig.",
"inviteErrorWrongUser": "Einladung ist nicht für diesen Benutzer",
"inviteErrorUserNotExists": "Benutzer existiert nicht. Bitte erstelle zuerst ein Konto.",
@@ -1374,6 +1460,8 @@
"sidebarResources": "Ressourcen",
"sidebarProxyResources": "Öffentlich",
"sidebarClientResources": "Privat",
"sidebarPolicies": "Policies",
"sidebarResourcePolicies": "Resources",
"sidebarAccessControl": "Zugriffskontrolle",
"sidebarLogsAndAnalytics": "Protokolle & Analysen",
"sidebarTeam": "Team",
@@ -1557,7 +1645,8 @@
"standaloneHcFilterSiteIdFallback": "Standort {id}",
"standaloneHcFilterResourceIdFallback": "Ressource {id}",
"blueprints": "Blaupausen",
"blueprintsDescription": "Deklarative Konfigurationen anwenden und vorherige Abläufe anzeigen",
"blueprintsLog": "Blueprints Log",
"blueprintsDescription": "View past blueprint applications and their results",
"blueprintAdd": "Blueprint hinzufügen",
"blueprintGoBack": "Alle Blueprints ansehen",
"blueprintCreate": "Blueprint erstellen",
@@ -1575,7 +1664,17 @@
"contents": "Inhalt",
"parsedContents": "Analysierte Inhalte (Nur lesen)",
"enableDockerSocket": "Docker Blueprint aktivieren",
"enableDockerSocketDescription": "Aktiviere Docker-Socket-Label-Scraping für Blueprintbeschriftungen. Der Socket-Pfad muss neu angegeben werden.",
"enableDockerSocketDescription": "Enable Docker Socket label scraping for blueprint labels. Socket path must be provided to the site connector. Read about how this works in <docsLink>the documentation</docsLink>.",
"newtAutoUpdate": "Enable Site Auto-Update",
"newtAutoUpdateDescription": "When enabled, site connectors will automatically update to the latest version when a new release is available.",
"siteAutoUpdate": "Site Auto-Update",
"siteAutoUpdateLabel": "Enable Auto-Update",
"siteAutoUpdateDescription": "Control whether this site's connector automatically downloads the latest version.",
"siteAutoUpdateOrgDefault": "Organization default: {state}",
"siteAutoUpdateOverriding": "Overriding organization setting",
"siteAutoUpdateResetToOrg": "Reset to Organization Default",
"siteAutoUpdateEnabled": "enabled",
"siteAutoUpdateDisabled": "disabled",
"viewDockerContainers": "Docker Container anzeigen",
"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.",
@@ -1620,6 +1719,7 @@
"certificateStatus": "Zertifikat",
"certificateStatusAutoRefreshHint": "Der Status wird automatisch aktualisiert.",
"loading": "Laden",
"loadingEllipsis": "Loading...",
"loadingAnalytics": "Analytik wird geladen",
"restart": "Neustart",
"domains": "Domänen",
@@ -1846,6 +1946,7 @@
"billingManageLicenseSubscription": "Verwalten Sie Ihr Abonnement für kostenpflichtige selbstgehostete Lizenzschlüssel",
"billingCurrentKeys": "Aktuelle Tasten",
"billingModifyCurrentPlan": "Aktuelles Paket ändern",
"billingManageLicenseSubscriptionDescription": "Manage your subscription for paid self-hosted license keys and download invoices.",
"billingConfirmUpgrade": "Upgrade bestätigen",
"billingConfirmDowngrade": "Downgrade bestätigen",
"billingConfirmUpgradeDescription": "Sie sind dabei, Ihr Paket zu aktualisieren. Schauen Sie sich die neuen Limits und Preise unten an.",
@@ -1943,7 +2044,36 @@
"timeIsInSeconds": "Zeit ist in Sekunden",
"requireDeviceApproval": "Gerätegenehmigungen erforderlich",
"requireDeviceApprovalDescription": "Benutzer mit dieser Rolle benötigen neue Geräte, die von einem Administrator genehmigt wurden, bevor sie sich verbinden und auf Ressourcen zugreifen können.",
"sshAccess": "SSH-Zugriff",
"sshSettings": "SSH Settings",
"rdpSettings": "RDP Settings",
"vncSettings": "VNC Settings",
"sshServer": "SSH Server",
"rdpServer": "RDP Server",
"vncServer": "VNC Server",
"sshServerDescription": "Set up the authentication method, daemon location, and server destination",
"rdpServerDescription": "Configure the destination and port of the RDP server",
"vncServerDescription": "Configure the destination and port of the VNC server",
"sshServerMode": "Mode",
"sshServerModeStandard": "Standard SSH Server",
"sshServerModePangolin": "Pangolin SSH",
"sshServerModeStandardDescription": "Routes commands over network to an SSH server such as OpenSSH.",
"sshServerModeNative": "Native SSH Server",
"sshServerModeNativeDescription": "Executes commands directly on the host via the Site Connector. No network config required.",
"sshAuthenticationMethod": "Authentication Method",
"sshAuthMethodManual": "Manual Authentication",
"sshAuthMethodManualDescription": "Requires existing host credentials. Bypasses automatic provisioning.",
"sshAuthMethodAutomated": "Automated Provisioning",
"sshAuthMethodAutomatedDescription": "Automatically creates users, groups, and sudo permissions on host.",
"sshAuthDaemonLocation": "Auth Daemon Location",
"sshDaemonLocationSiteDescription": "Executes locally on the machine hosting the site connector.",
"sshDaemonLocationRemote": "On Remote Host",
"sshDaemonLocationRemoteDescription": "Executes on a separate target machine on the same network.",
"sshDaemonDisclaimer": "Ensure your target host is properly configured to run the auth daemon before completing this setup, or provisioning will fail.",
"sshDaemonPort": "Daemon Port",
"sshServerDestination": "Server Destination",
"sshServerDestinationDescription": "Configure the destination and port of the SSH server",
"destination": "Destination",
"bgTargetMultiSiteDisclaimer": "Selecting multiple sites enables resilient routing and failover for high availability.",
"roleAllowSsh": "SSH erlauben",
"roleAllowSshAllow": "Erlauben",
"roleAllowSshDisallow": "Nicht zulassen",
@@ -1957,7 +2087,7 @@
"sshSudoModeCommandsDescription": "Benutzer kann nur die angegebenen Befehle mit sudo ausführen.",
"sshSudo": "sudo erlauben",
"sshSudoCommands": "Sudo-Befehle",
"sshSudoCommandsDescription": "Kommagetrennte Liste von Befehlen, die der Benutzer mit sudo ausführen darf.",
"sshSudoCommandsDescription": "Comma separated list of commands the user is allowed to run with sudo. Absolute paths must be used.",
"sshCreateHomeDir": "Home-Verzeichnis erstellen",
"sshUnixGroups": "Unix-Gruppen",
"sshUnixGroupsDescription": "Durch Komma getrennte Unix-Gruppen, um den Benutzer auf dem Zielhost hinzuzufügen.",
@@ -2049,8 +2179,9 @@
"editInternalResourceDialogModeCidr": "CIDR",
"editInternalResourceDialogModeHttp": "HTTP",
"editInternalResourceDialogModeHttps": "HTTPS",
"editInternalResourceDialogModeSsh": "SSH",
"editInternalResourceDialogScheme": "Schema",
"editInternalResourceDialogEnableSsl": "TLS aktivieren",
"editInternalResourceDialogEnableSsl": "Enable TLS",
"editInternalResourceDialogEnableSslDescription": "SSL/TLS-Verschlüsselung für sichere HTTPS-Verbindungen zum Ziel aktivieren.",
"editInternalResourceDialogDestination": "Ziel",
"editInternalResourceDialogDestinationHostDescription": "Die IP-Adresse oder der Hostname der Ressource im Netzwerk der Website.",
@@ -2098,9 +2229,10 @@
"createInternalResourceDialogModeCidr": "CIDR",
"createInternalResourceDialogModeHttp": "HTTP",
"createInternalResourceDialogModeHttps": "HTTPS",
"createInternalResourceDialogModeSsh": "SSH",
"scheme": "Schema",
"createInternalResourceDialogScheme": "Schema",
"createInternalResourceDialogEnableSsl": "TLS aktivieren",
"createInternalResourceDialogEnableSsl": "Enable TLS",
"createInternalResourceDialogEnableSslDescription": "SSL/TLS-Verschlüsselung für sichere HTTPS-Verbindungen zum Ziel aktivieren.",
"createInternalResourceDialogDestination": "Ziel",
"createInternalResourceDialogDestinationHostDescription": "Die IP-Adresse oder der Hostname der Ressource im Netzwerk der Website.",
@@ -2233,7 +2365,7 @@
"description": "Zuverlässiger und wartungsarmer Pangolin Server mit zusätzlichen Glocken und Pfeifen",
"introTitle": "Verwalteter selbstgehosteter Pangolin",
"introDescription": "ist eine Deployment-Option, die für Personen konzipiert wurde, die Einfachheit und zusätzliche Zuverlässigkeit wünschen, während sie ihre Daten privat und selbstgehostet halten.",
"introDetail": "Mit dieser Option haben Sie immer noch Ihren eigenen Pangolin-Knoten Ihre Tunnel, TLS-Terminierung und Traffic bleiben auf Ihrem Server. Der Unterschied besteht darin, dass Verwaltung und Überwachung über unser Cloud-Dashboard abgewickelt werden, das eine Reihe von Vorteilen freischaltet:",
"introDetail": "With this option, you still run your own Pangolin node - your tunnels, TLS termination, and traffic all stay on your server. The difference is that management and monitoring are handled through our cloud dashboard, which unlocks a number of benefits:",
"benefitSimplerOperations": {
"title": "Einfachere Operationen",
"description": "Sie brauchen keinen eigenen Mail-Server auszuführen oder komplexe Warnungen einzurichten. Sie erhalten Gesundheitschecks und Ausfallwarnungen aus dem Box."
@@ -2937,7 +3069,7 @@
"learnMore": "Mehr erfahren",
"backToHome": "Zurück zur Startseite",
"needToSignInToOrg": "Benötigen Sie den Identitätsanbieter Ihres Unternehmens?",
"maintenanceMode": "Wartungsmodus",
"maintenanceMode": "Maintenance Page",
"maintenanceModeDescription": "Eine Wartungsseite für Besucher anzeigen",
"maintenanceModeType": "Art des Wartungsmodus",
"showMaintenancePage": "Eine Wartungsseite für Besucher anzeigen",
@@ -2967,6 +3099,7 @@
"maintenanceScreenEstimatedCompletion": "Geschätzter Abschluss:",
"createInternalResourceDialogDestinationRequired": "Ziel ist erforderlich",
"available": "Verfügbar",
"disabledResourceDescription": "When disabled, the resource will be inaccessible by everyone.",
"archived": "Archiviert",
"noArchivedDevices": "Keine archivierten Geräte gefunden",
"deviceArchived": "Gerät archiviert",
@@ -3296,5 +3429,6 @@
"memberPortalResourceDisabled": "Ressource deaktiviert",
"memberPortalShowingResources": "Zeige {start}-{end} von {total} Ressourcen",
"memberPortalPrevious": "Vorherige",
"memberPortalNext": "Nächste"
"memberPortalNext": "Nächste",
"httpSettings": "HTTP Settings"
}

View File

@@ -208,11 +208,33 @@
"resourcesSearch": "Search resources...",
"resourceAdd": "Add Resource",
"resourceErrorDelte": "Error deleting resource",
"resourcePoliciesTitle": "Manage Resource Policies",
"resourcePoliciesAttachedResourcesColumnTitle": "Attached resources",
"resourcePoliciesAttachedResources": "{count} resource(s)",
"resourcePoliciesAttachedResourcesEmpty": "no resources",
"resourcePoliciesDescription": "Create and manage authentication policies to control access to your resources",
"resourcePoliciesSearch": "Search policies...",
"resourcePoliciesAdd": "Add Policy",
"resourcePoliciesDefaultBadgeText": "Default policy",
"resourcePoliciesCreate": "Create Resource Policy",
"resourcePoliciesCreateDescription": "Follow the steps below to create a new policy",
"resourcePolicyName": "Policy Name",
"resourcePolicyNameDescription": "Give this policy a name to identify it across your resources",
"resourcePolicyNamePlaceholder": "e.g. Internal Access Policy",
"resourcePoliciesSeeAll": "See All Policies",
"resourcePolicyAuthMethodAdd": "Add Authentication Method",
"resourcePolicyOtpEmailAdd": "Add OTP emails",
"resourcePolicyRulesAdd": "Add Rules",
"resourcePolicyAuthMethodsDescription": "Allow access to resources via additional auth methods",
"resourcePolicyUsersRolesDescription": "Configure which users and roles can visit associated resources",
"rulesResourcePolicyDescription": "Configure rules to control access resources associated to this policy",
"authentication": "Authentication",
"protected": "Protected",
"notProtected": "Not Protected",
"resourceMessageRemove": "Once removed, the resource will no longer be accessible. All targets associated with the resource will also be removed.",
"resourceQuestionRemove": "Are you sure you want to remove the resource from the organization?",
"resourcePolicyMessageRemove": "Once removed, the resource policy will no longer be accessible. All resources associated with the resource will be unlinked and left without authentication.",
"resourcePolicyQuestionRemove": "Are you sure you want to remove the resource policy from the organization?",
"resourceHTTP": "HTTPS Resource",
"resourceHTTPDescription": "Proxy requests over HTTPS using a fully qualified domain name.",
"resourceRaw": "Raw TCP/UDP Resource",
@@ -220,8 +242,9 @@
"resourceRawDescriptionCloud": "Proxy requests over raw TCP/UDP using a port number. Requires sites to connect to a remote node.",
"resourceCreate": "Create Resource",
"resourceCreateDescription": "Follow the steps below to create a new resource",
"resourceCreateGeneralDescription": "Configure the basic resource settings including the name and the type",
"resourceSeeAll": "See All Resources",
"resourceInfo": "Resource Information",
"resourceCreateGeneral": "General",
"resourceNameDescription": "This is the display name for the resource.",
"siteSelect": "Select site",
"siteSearch": "Search site",
@@ -231,12 +254,15 @@
"noCountryFound": "No country found.",
"siteSelectionDescription": "This site will provide connectivity to the target.",
"resourceType": "Resource Type",
"resourceTypeDescription": "Determine how to access the resource",
"resourceTypeDescription": "This controls the resource protocol and how it will be rendered in the browser. This cant be changed later.",
"resourceDomainDescription": "The resource will be served at this fully qualified domain name.",
"resourceHTTPSSettings": "HTTPS Settings",
"resourceHTTPSSettingsDescription": "Configure how the resource will be accessed over HTTPS",
"resourcePortDescription": "The external port on the Pangolin instance or node where the resource will be accessible.",
"domainType": "Domain Type",
"subdomain": "Subdomain",
"baseDomain": "Base Domain",
"configure": "Configure",
"subdomnainDescription": "The subdomain where the resource will be accessible.",
"resourceRawSettings": "TCP/UDP Settings",
"resourceRawSettingsDescription": "Configure how the resource will be accessed over TCP/UDP",
@@ -253,8 +279,27 @@
"resourceLearnRaw": "Learn how to configure TCP/UDP resources",
"resourceBack": "Back to Resources",
"resourceGoTo": "Go to Resource",
"resourcePolicyDelete": "Delete Resource Policy",
"resourcePolicyDeleteConfirm": "Confirm Delete Resource Policy",
"resourceDelete": "Delete Resource",
"resourceDeleteConfirm": "Confirm Delete Resource",
"labelDelete": "Delete Label",
"labelAdd": "Add Label",
"labelCreateSuccessMessage": "Label Created Successfully",
"labelEditSuccessMessage": "Label Modified Successfully",
"labelNameField": "Label Name",
"labelColorField": "Label Color",
"labelPlaceholder": "Ex: homelab",
"labelCreate": "Create Label",
"createLabelDialogTitle": "Create Label",
"createLabelDialogDescription": "Create a new label that can be attached to this organization",
"labelEdit": "Edit Label",
"editLabelDialogTitle": "Update Label",
"editLabelDialogDescription": "Edit a new label that can be attached to this organization",
"labelDeleteConfirm": "Confirm Delete Label",
"labelErrorDelete": "Failed to delete label",
"labelMessageRemove": "This action is permanent. All sites, resources, and clients tagged with this label will be untagged.",
"labelQuestionRemove": "Are you sure you want to remove the label from the organization?",
"visibility": "Visibility",
"enabled": "Enabled",
"disabled": "Disabled",
@@ -265,6 +310,8 @@
"rules": "Rules",
"resourceSettingDescription": "Configure the settings on the resource",
"resourceSetting": "{resourceName} Settings",
"resourcePolicySettingDescription": "Configure the settings on the resource policy",
"resourcePolicySetting": "{policyName} Settings",
"alwaysAllow": "Bypass Auth",
"alwaysDeny": "Block Access",
"passToAuth": "Pass to Auth",
@@ -747,6 +794,16 @@
"rulesNoOne": "No rules. Add a rule using the form.",
"rulesOrder": "Rules are evaluated by priority in ascending order.",
"rulesSubmit": "Save Rules",
"policyErrorCreate": "Error creating policy",
"policyErrorCreateDescription": "An error occurred when creating the policy",
"policyErrorCreateMessageDescription": "An unexpected error occurred",
"policyErrorUpdate": "Error updating policy",
"policyErrorUpdateDescription": "An error occurred when updating the policy",
"policyErrorUpdateMessageDescription": "An unexpected error occurred",
"policyCreatedSuccess": "Resource policy succesfully created",
"policyUpdatedSuccess": "Resource policy succesfully updated",
"authMethodsSave": "Save auth methods",
"rulesSave": "Save Rules",
"resourceErrorCreate": "Error creating resource",
"resourceErrorCreateDescription": "An error occurred when creating the resource",
"resourceErrorCreateMessage": "Error creating resource:",
@@ -810,6 +867,16 @@
"pincodeAdd": "Add PIN Code",
"pincodeRemove": "Remove PIN Code",
"resourceAuthMethods": "Authentication Methods",
"resourcePolicyAuthMethodsEmpty": "No authentication method",
"resourcePolicyOtpEmpty": "No one time password",
"resourcePolicyReadOnly": "This policy is Read only",
"resourcePolicyReadOnlyDescription": "This resource policy is shared accross multiple resources, you cannot edit it on this page.",
"resourcePolicyTypeSave": "Save Resource type",
"resourcePolicySelect": "Select resource policy",
"resourcePolicySelectError": "Select a resource policy",
"resourcePolicyNotFound": "Policy not found",
"resourcePolicySearch": "Search policies",
"resourcePolicyRulesEmpty": "No authentication rules",
"resourceAuthMethodsDescriptions": "Allow access to the resource via additional auth methods",
"resourceAuthSettingsSave": "Saved successfully",
"resourceAuthSettingsSaveDescription": "Authentication settings have been saved",
@@ -845,6 +912,12 @@
"resourcePincodeSetupTitle": "Set Pincode",
"resourcePincodeSetupTitleDescription": "Set a pincode to protect this resource",
"resourceRoleDescription": "Admins can always access this resource.",
"resourcePolicySelectTitle": "Resource Access Policy",
"resourcePolicySelectDescription": "Select the resource policy type for authentication",
"resourcePolicyInline": "Inline Resource Policy",
"resourcePolicyInlineDescription": "Access Policy scoped to only this resource",
"resourcePolicyShared": "Shared Resource Policy",
"resourcePolicySharedDescription": "Access Policy shared accross multiple resources",
"resourceUsersRoles": "Access Controls",
"resourceUsersRolesDescription": "Configure which users and roles can visit this resource",
"resourceUsersRolesSubmit": "Save Access Controls",
@@ -1140,6 +1213,18 @@
"idpErrorConnectingTo": "There was a problem connecting to {name}. Please contact your administrator.",
"idpErrorNotFound": "IdP not found",
"inviteInvalid": "Invalid Invite",
"labels": "Labels",
"orgLabelsDescription": "Manage labels in this organization.",
"addLabels": "Add labels",
"siteLabelsTab": "Labels",
"siteLabelsDescription": "Manage labels associated with this site.",
"labelsNotFound": "Labels not found",
"labelSearch": "Search labels",
"accessLabelFilterCount": "{count, plural, one {# label} other {# labels}}",
"labelOverflowCount": "+{count, plural, one {# label} other {# labels}}",
"accessLabelFilterClear": "Clear label filters",
"selectColor": "Select color",
"createNewLabel": "Create new org label \"{label}\"",
"inviteInvalidDescription": "The invite link is invalid.",
"inviteErrorWrongUser": "Invite is not for this user",
"inviteErrorUserNotExists": "User does not exist. Please create an account first.",
@@ -1374,6 +1459,8 @@
"sidebarResources": "Resources",
"sidebarProxyResources": "Public",
"sidebarClientResources": "Private",
"sidebarPolicies": "Policies",
"sidebarResourcePolicies": "Resources",
"sidebarAccessControl": "Access Control",
"sidebarLogsAndAnalytics": "Logs & Analytics",
"sidebarTeam": "Team",
@@ -1557,7 +1644,8 @@
"standaloneHcFilterSiteIdFallback": "Site {id}",
"standaloneHcFilterResourceIdFallback": "Resource {id}",
"blueprints": "Blueprints",
"blueprintsDescription": "Apply declarative configurations and view previous runs",
"blueprintsLog": "Blueprints Log",
"blueprintsDescription": "View past blueprint applications and their results",
"blueprintAdd": "Add Blueprint",
"blueprintGoBack": "See all Blueprints",
"blueprintCreate": "Create Blueprint",
@@ -1575,7 +1663,17 @@
"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. Read about how this works in <docsLink>the documentation</docsLink>.",
"enableDockerSocketDescription": "Enable Docker Socket label scraping for blueprint labels. Socket path must be provided to the site connector. Read about how this works in <docsLink>the documentation</docsLink>.",
"newtAutoUpdate": "Enable Site Auto-Update",
"newtAutoUpdateDescription": "When enabled, site connectors will automatically update to the latest version when a new release is available.",
"siteAutoUpdate": "Site Auto-Update",
"siteAutoUpdateLabel": "Enable Auto-Update",
"siteAutoUpdateDescription": "Control whether this site's connector automatically downloads the latest version.",
"siteAutoUpdateOrgDefault": "Organization default: {state}",
"siteAutoUpdateOverriding": "Overriding organization setting",
"siteAutoUpdateResetToOrg": "Reset to Organization Default",
"siteAutoUpdateEnabled": "enabled",
"siteAutoUpdateDisabled": "disabled",
"viewDockerContainers": "View Docker Containers",
"containersIn": "Containers in {siteName}",
"selectContainerDescription": "Select any container to use as a hostname for this target. Click a port to use a port.",
@@ -1620,6 +1718,7 @@
"certificateStatus": "Certificate",
"certificateStatusAutoRefreshHint": "Status refreshes automatically.",
"loading": "Loading",
"loadingEllipsis": "Loading...",
"loadingAnalytics": "Loading Analytics",
"restart": "Restart",
"domains": "Domains",
@@ -1846,6 +1945,7 @@
"billingManageLicenseSubscription": "Manage your subscription for paid self-hosted license keys",
"billingCurrentKeys": "Current Keys",
"billingModifyCurrentPlan": "Modify Current Plan",
"billingManageLicenseSubscriptionDescription": "Manage your subscription for paid self-hosted license keys and download invoices.",
"billingConfirmUpgrade": "Confirm Upgrade",
"billingConfirmDowngrade": "Confirm Downgrade",
"billingConfirmUpgradeDescription": "You are about to upgrade your plan. Review the new limits and pricing below.",
@@ -1943,7 +2043,36 @@
"timeIsInSeconds": "Time is in seconds",
"requireDeviceApproval": "Require Device Approvals",
"requireDeviceApprovalDescription": "Users with this role need new devices approved by an admin before they can connect and access resources.",
"sshAccess": "SSH Access",
"sshSettings": "SSH Settings",
"rdpSettings": "RDP Settings",
"vncSettings": "VNC Settings",
"sshServer": "SSH Server",
"rdpServer": "RDP Server",
"vncServer": "VNC Server",
"sshServerDescription": "Set up the authentication method, daemon location, and server destination",
"rdpServerDescription": "Configure the destination and port of the RDP server",
"vncServerDescription": "Configure the destination and port of the VNC server",
"sshServerMode": "Mode",
"sshServerModeStandard": "Standard SSH Server",
"sshServerModePangolin": "Pangolin SSH",
"sshServerModeStandardDescription": "Routes commands over network to an SSH server such as OpenSSH.",
"sshServerModeNative": "Native SSH Server",
"sshServerModeNativeDescription": "Executes commands directly on the host via the Site Connector. No network config required.",
"sshAuthenticationMethod": "Authentication Method",
"sshAuthMethodManual": "Manual Authentication",
"sshAuthMethodManualDescription": "Requires existing host credentials. Bypasses automatic provisioning.",
"sshAuthMethodAutomated": "Automated Provisioning",
"sshAuthMethodAutomatedDescription": "Automatically creates users, groups, and sudo permissions on host.",
"sshAuthDaemonLocation": "Auth Daemon Location",
"sshDaemonLocationSiteDescription": "Executes locally on the machine hosting the site connector.",
"sshDaemonLocationRemote": "On Remote Host",
"sshDaemonLocationRemoteDescription": "Executes on a separate target machine on the same network.",
"sshDaemonDisclaimer": "Ensure your target host is properly configured to run the auth daemon before completing this setup, or provisioning will fail.",
"sshDaemonPort": "Daemon Port",
"sshServerDestination": "Server Destination",
"sshServerDestinationDescription": "Configure the destination and port of the SSH server",
"destination": "Destination",
"bgTargetMultiSiteDisclaimer": "Selecting multiple sites enables resilient routing and failover for high availability.",
"roleAllowSsh": "Allow SSH",
"roleAllowSshAllow": "Allow",
"roleAllowSshDisallow": "Disallow",
@@ -2049,6 +2178,7 @@
"editInternalResourceDialogModeCidr": "CIDR",
"editInternalResourceDialogModeHttp": "HTTP",
"editInternalResourceDialogModeHttps": "HTTPS",
"editInternalResourceDialogModeSsh": "SSH",
"editInternalResourceDialogScheme": "Scheme",
"editInternalResourceDialogEnableSsl": "Enable TLS",
"editInternalResourceDialogEnableSslDescription": "Enable SSL/TLS encryption for secure HTTPS connections to the destination.",
@@ -2098,6 +2228,7 @@
"createInternalResourceDialogModeCidr": "CIDR",
"createInternalResourceDialogModeHttp": "HTTP",
"createInternalResourceDialogModeHttps": "HTTPS",
"createInternalResourceDialogModeSsh": "SSH",
"scheme": "Scheme",
"createInternalResourceDialogScheme": "Scheme",
"createInternalResourceDialogEnableSsl": "Enable TLS",
@@ -2937,7 +3068,7 @@
"learnMore": "Learn more",
"backToHome": "Go back to home",
"needToSignInToOrg": "Need to use your organization's identity provider?",
"maintenanceMode": "Maintenance Mode",
"maintenanceMode": "Maintenance Page",
"maintenanceModeDescription": "Display a maintenance page to visitors",
"maintenanceModeType": "Maintenance Mode Type",
"showMaintenancePage": "Show a maintenance page to visitors",
@@ -2967,6 +3098,7 @@
"maintenanceScreenEstimatedCompletion": "Estimated Completion:",
"createInternalResourceDialogDestinationRequired": "Destination is required",
"available": "Available",
"disabledResourceDescription": "When disabled, the resource will be inaccessible by everyone.",
"archived": "Archived",
"noArchivedDevices": "No archived devices found",
"deviceArchived": "Device archived",
@@ -3296,5 +3428,6 @@
"memberPortalResourceDisabled": "Resource Disabled",
"memberPortalShowingResources": "Showing {start}-{end} of {total} resources",
"memberPortalPrevious": "Previous",
"memberPortalNext": "Next"
"memberPortalNext": "Next",
"httpSettings": "HTTP Settings"
}

View File

@@ -176,6 +176,7 @@
"shareErrorCreateDescription": "Se ha producido un error al crear el enlace compartido",
"shareCreateDescription": "Cualquiera con este enlace puede acceder al recurso",
"shareTitleOptional": "Título (opcional)",
"sharePathOptional": "Path (optional)",
"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.",
@@ -208,11 +209,33 @@
"resourcesSearch": "Buscar recursos...",
"resourceAdd": "Añadir Recurso",
"resourceErrorDelte": "Error al eliminar el recurso",
"resourcePoliciesTitle": "Manage Resource Policies",
"resourcePoliciesAttachedResourcesColumnTitle": "Attached resources",
"resourcePoliciesAttachedResources": "{count} resource(s)",
"resourcePoliciesAttachedResourcesEmpty": "no resources",
"resourcePoliciesDescription": "Create and manage authentication policies to control access to your resources",
"resourcePoliciesSearch": "Search policies...",
"resourcePoliciesAdd": "Add Policy",
"resourcePoliciesDefaultBadgeText": "Default policy",
"resourcePoliciesCreate": "Create Resource Policy",
"resourcePoliciesCreateDescription": "Follow the steps below to create a new policy",
"resourcePolicyName": "Policy Name",
"resourcePolicyNameDescription": "Give this policy a name to identify it across your resources",
"resourcePolicyNamePlaceholder": "e.g. Internal Access Policy",
"resourcePoliciesSeeAll": "See All Policies",
"resourcePolicyAuthMethodAdd": "Add Authentication Method",
"resourcePolicyOtpEmailAdd": "Add OTP emails",
"resourcePolicyRulesAdd": "Add Rules",
"resourcePolicyAuthMethodsDescription": "Allow access to resources via additional auth methods",
"resourcePolicyUsersRolesDescription": "Configure which users and roles can visit associated resources",
"rulesResourcePolicyDescription": "Configure rules to control access resources associated to this policy",
"authentication": "Autenticación",
"protected": "Protegido",
"notProtected": "No protegido",
"resourceMessageRemove": "Una vez eliminado, el recurso ya no será accesible. Todos los objetivos asociados con el recurso también serán eliminados.",
"resourceQuestionRemove": "¿Está seguro que desea eliminar el recurso de la organización?",
"resourcePolicyMessageRemove": "Once removed, the resource policy will no longer be accessible. All resources associated with the resource will be unlinked and left without authentication.",
"resourcePolicyQuestionRemove": "Are you sure you want to remove the resource policy from the organization?",
"resourceHTTP": "HTTPS Recurso",
"resourceHTTPDescription": "Proxy proporciona solicitudes sobre HTTPS usando un nombre de dominio completamente calificado.",
"resourceRaw": "Recurso TCP/UDP sin procesar",
@@ -220,8 +243,9 @@
"resourceRawDescriptionCloud": "Las peticiones de proxy sobre TCP/UDP crudas usando un número de puerto. Requiere que los sitios se conecten a un nodo remoto.",
"resourceCreate": "Crear Recurso",
"resourceCreateDescription": "Siga los siguientes pasos para crear un nuevo recurso",
"resourceCreateGeneralDescription": "Configure the basic resource settings including the name and the type",
"resourceSeeAll": "Ver todos los recursos",
"resourceInfo": "Información del recurso",
"resourceCreateGeneral": "General",
"resourceNameDescription": "Este es el nombre para mostrar el recurso.",
"siteSelect": "Seleccionar sitio",
"siteSearch": "Buscar sitio",
@@ -231,12 +255,15 @@
"noCountryFound": "Ningún país encontrado.",
"siteSelectionDescription": "Este sitio proporcionará conectividad al objetivo.",
"resourceType": "Tipo de recurso",
"resourceTypeDescription": "Determina cómo acceder al recurso",
"resourceTypeDescription": "This controls the resource protocol and how it will be rendered in the browser. This cant be changed later.",
"resourceDomainDescription": "The resource will be served at this fully qualified domain name.",
"resourceHTTPSSettings": "Configuración HTTPS",
"resourceHTTPSSettingsDescription": "Configurar cómo se accederá al recurso a través de HTTPS",
"resourcePortDescription": "The external port on the Pangolin instance or node where the resource will be accessible.",
"domainType": "Tipo de dominio",
"subdomain": "Subdominio",
"baseDomain": "Dominio base",
"configure": "Configure",
"subdomnainDescription": "El subdominio al que el recurso será accesible.",
"resourceRawSettings": "Configuración TCP/UDP",
"resourceRawSettingsDescription": "Configurar cómo se accederá al recurso a través de TCP/UDP",
@@ -253,8 +280,27 @@
"resourceLearnRaw": "Aprende cómo configurar los recursos TCP/UDP",
"resourceBack": "Volver a Recursos",
"resourceGoTo": "Ir a Recurso",
"resourcePolicyDelete": "Delete Resource Policy",
"resourcePolicyDeleteConfirm": "Confirm Delete Resource Policy",
"resourceDelete": "Eliminar Recurso",
"resourceDeleteConfirm": "Confirmar Borrar Recurso",
"labelDelete": "Delete Label",
"labelAdd": "Add Label",
"labelCreateSuccessMessage": "Label Created Successfully",
"labelEditSuccessMessage": "Label Modified Successfully",
"labelNameField": "Label Name",
"labelColorField": "Label Color",
"labelPlaceholder": "Ex: homelab",
"labelCreate": "Create Label",
"createLabelDialogTitle": "Create Label",
"createLabelDialogDescription": "Create a new label that can be attached to this organization",
"labelEdit": "Edit Label",
"editLabelDialogTitle": "Update Label",
"editLabelDialogDescription": "Edit a new label that can be attached to this organization",
"labelDeleteConfirm": "Confirm Delete Label",
"labelErrorDelete": "Failed to delete label",
"labelMessageRemove": "This action is permanent. All sites, resources, and clients tagged with this label will be untagged.",
"labelQuestionRemove": "Are you sure you want to remove the label from the organization?",
"visibility": "Visibilidad",
"enabled": "Activado",
"disabled": "Deshabilitado",
@@ -265,6 +311,8 @@
"rules": "Reglas",
"resourceSettingDescription": "Configurar la configuración del recurso",
"resourceSetting": "Ajustes {resourceName}",
"resourcePolicySettingDescription": "Configure the settings on the resource policy",
"resourcePolicySetting": "{policyName} Settings",
"alwaysAllow": "Autorización Bypass",
"alwaysDeny": "Bloquear acceso",
"passToAuth": "Pasar a Autenticación",
@@ -630,7 +678,7 @@
"createdAt": "Creado el",
"proxyErrorInvalidHeader": "Valor de cabecera de host personalizado no válido. Utilice el formato de nombre de dominio, o guarde en blanco para desestablecer cabecera de host personalizada.",
"proxyErrorTls": "Nombre de servidor TLS inválido. Utilice el formato de nombre de dominio o guarde en blanco para eliminar el nombre de servidor TLS.",
"proxyEnableSSL": "Activar TLS",
"proxyEnableSSL": "Enable TLS",
"proxyEnableSSLDescription": "Habilita el cifrado SSL/TLS para conexiones seguras HTTPS a los objetivos.",
"target": "Target",
"configureTarget": "Configurar objetivos",
@@ -747,6 +795,16 @@
"rulesNoOne": "No hay reglas. Agregue una regla usando el formulario.",
"rulesOrder": "Las reglas son evaluadas por prioridad en orden ascendente.",
"rulesSubmit": "Guardar Reglas",
"policyErrorCreate": "Error creating policy",
"policyErrorCreateDescription": "An error occurred when creating the policy",
"policyErrorCreateMessageDescription": "An unexpected error occurred",
"policyErrorUpdate": "Error updating policy",
"policyErrorUpdateDescription": "An error occurred when updating the policy",
"policyErrorUpdateMessageDescription": "An unexpected error occurred",
"policyCreatedSuccess": "Resource policy succesfully created",
"policyUpdatedSuccess": "Resource policy succesfully updated",
"authMethodsSave": "Save auth methods",
"rulesSave": "Save Rules",
"resourceErrorCreate": "Error al crear recurso",
"resourceErrorCreateDescription": "Se ha producido un error al crear el recurso",
"resourceErrorCreateMessage": "Error al crear el recurso:",
@@ -810,6 +868,16 @@
"pincodeAdd": "Añadir código PIN",
"pincodeRemove": "Eliminar código PIN",
"resourceAuthMethods": "Métodos de autenticación",
"resourcePolicyAuthMethodsEmpty": "No authentication method",
"resourcePolicyOtpEmpty": "No one time password",
"resourcePolicyReadOnly": "This policy is Read only",
"resourcePolicyReadOnlyDescription": "This resource policy is shared accross multiple resources, you cannot edit it on this page.",
"resourcePolicyTypeSave": "Save Resource type",
"resourcePolicySelect": "Select resource policy",
"resourcePolicySelectError": "Select a resource policy",
"resourcePolicyNotFound": "Policy not found",
"resourcePolicySearch": "Search policies",
"resourcePolicyRulesEmpty": "No authentication rules",
"resourceAuthMethodsDescriptions": "Permitir el acceso al recurso a través de métodos de autenticación adicionales",
"resourceAuthSettingsSave": "Guardado correctamente",
"resourceAuthSettingsSaveDescription": "Se han guardado los ajustes de autenticación",
@@ -845,6 +913,12 @@
"resourcePincodeSetupTitle": "Definir Pincode",
"resourcePincodeSetupTitleDescription": "Establecer un pincode para proteger este recurso",
"resourceRoleDescription": "Los administradores siempre pueden acceder a este recurso.",
"resourcePolicySelectTitle": "Resource Access Policy",
"resourcePolicySelectDescription": "Select the resource policy type for authentication",
"resourcePolicyInline": "Inline Resource Policy",
"resourcePolicyInlineDescription": "Access Policy scoped to only this resource",
"resourcePolicyShared": "Shared Resource Policy",
"resourcePolicySharedDescription": "Access Policy shared accross multiple resources",
"resourceUsersRoles": "Controles de acceso",
"resourceUsersRolesDescription": "Configurar qué usuarios y roles pueden visitar este recurso",
"resourceUsersRolesSubmit": "Guardar controles de acceso",
@@ -1140,6 +1214,18 @@
"idpErrorConnectingTo": "Hubo un problema al conectar con {name}. Por favor, póngase en contacto con su administrador.",
"idpErrorNotFound": "IdP no encontrado",
"inviteInvalid": "Invitación inválida",
"labels": "Labels",
"orgLabelsDescription": "Manage labels in this organization.",
"addLabels": "Add labels",
"siteLabelsTab": "Labels",
"siteLabelsDescription": "Manage labels associated with this site.",
"labelsNotFound": "Labels not found",
"labelSearch": "Search labels",
"accessLabelFilterCount": "{count, plural, one {# label} other {# labels}}",
"labelOverflowCount": "+{count, plural, one {# label} other {# labels}}",
"accessLabelFilterClear": "Clear label filters",
"selectColor": "Select color",
"createNewLabel": "Create new org label \"{label}\"",
"inviteInvalidDescription": "El enlace de invitación no es válido.",
"inviteErrorWrongUser": "La invitación no es para este usuario",
"inviteErrorUserNotExists": "El usuario no existe. Por favor, cree una cuenta primero.",
@@ -1374,6 +1460,8 @@
"sidebarResources": "Recursos",
"sidebarProxyResources": "Público",
"sidebarClientResources": "Privado",
"sidebarPolicies": "Policies",
"sidebarResourcePolicies": "Resources",
"sidebarAccessControl": "Control de acceso",
"sidebarLogsAndAnalytics": "Registros y análisis",
"sidebarTeam": "Equipo",
@@ -1557,7 +1645,8 @@
"standaloneHcFilterSiteIdFallback": "Sitio {id}",
"standaloneHcFilterResourceIdFallback": "Recurso {id}",
"blueprints": "Planos",
"blueprintsDescription": "Aplicar configuraciones declarativas y ver ejecuciones anteriores",
"blueprintsLog": "Blueprints Log",
"blueprintsDescription": "View past blueprint applications and their results",
"blueprintAdd": "Añadir plano",
"blueprintGoBack": "Ver todos los Planos",
"blueprintCreate": "Crear Plano",
@@ -1575,7 +1664,17 @@
"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.",
"enableDockerSocketDescription": "Enable Docker Socket label scraping for blueprint labels. Socket path must be provided to the site connector. Read about how this works in <docsLink>the documentation</docsLink>.",
"newtAutoUpdate": "Enable Site Auto-Update",
"newtAutoUpdateDescription": "When enabled, site connectors will automatically update to the latest version when a new release is available.",
"siteAutoUpdate": "Site Auto-Update",
"siteAutoUpdateLabel": "Enable Auto-Update",
"siteAutoUpdateDescription": "Control whether this site's connector automatically downloads the latest version.",
"siteAutoUpdateOrgDefault": "Organization default: {state}",
"siteAutoUpdateOverriding": "Overriding organization setting",
"siteAutoUpdateResetToOrg": "Reset to Organization Default",
"siteAutoUpdateEnabled": "enabled",
"siteAutoUpdateDisabled": "disabled",
"viewDockerContainers": "Ver contenedores Docker",
"containersIn": "Contenedores en {siteName}",
"selectContainerDescription": "Seleccione cualquier contenedor para usar como nombre de host para este objetivo. Haga clic en un puerto para usar un puerto.",
@@ -1620,6 +1719,7 @@
"certificateStatus": "Certificado",
"certificateStatusAutoRefreshHint": "El estado se actualiza automáticamente.",
"loading": "Cargando",
"loadingEllipsis": "Loading...",
"loadingAnalytics": "Cargando analíticas",
"restart": "Reiniciar",
"domains": "Dominios",
@@ -1846,6 +1946,7 @@
"billingManageLicenseSubscription": "Administra tu suscripción para las claves de licencia autoalojadas pagadas",
"billingCurrentKeys": "Claves actuales",
"billingModifyCurrentPlan": "Modificar plan actual",
"billingManageLicenseSubscriptionDescription": "Manage your subscription for paid self-hosted license keys and download invoices.",
"billingConfirmUpgrade": "Confirmar actualización",
"billingConfirmDowngrade": "Confirmar descenso",
"billingConfirmUpgradeDescription": "Estás a punto de actualizar tu plan. Revisa los nuevos límites y precios a continuación.",
@@ -1943,7 +2044,36 @@
"timeIsInSeconds": "El tiempo está en segundos",
"requireDeviceApproval": "Requiere aprobaciones del dispositivo",
"requireDeviceApprovalDescription": "Los usuarios con este rol necesitan nuevos dispositivos aprobados por un administrador antes de poder conectarse y acceder a los recursos.",
"sshAccess": "Acceso a SSH",
"sshSettings": "SSH Settings",
"rdpSettings": "RDP Settings",
"vncSettings": "VNC Settings",
"sshServer": "SSH Server",
"rdpServer": "RDP Server",
"vncServer": "VNC Server",
"sshServerDescription": "Set up the authentication method, daemon location, and server destination",
"rdpServerDescription": "Configure the destination and port of the RDP server",
"vncServerDescription": "Configure the destination and port of the VNC server",
"sshServerMode": "Mode",
"sshServerModeStandard": "Standard SSH Server",
"sshServerModePangolin": "Pangolin SSH",
"sshServerModeStandardDescription": "Routes commands over network to an SSH server such as OpenSSH.",
"sshServerModeNative": "Native SSH Server",
"sshServerModeNativeDescription": "Executes commands directly on the host via the Site Connector. No network config required.",
"sshAuthenticationMethod": "Authentication Method",
"sshAuthMethodManual": "Manual Authentication",
"sshAuthMethodManualDescription": "Requires existing host credentials. Bypasses automatic provisioning.",
"sshAuthMethodAutomated": "Automated Provisioning",
"sshAuthMethodAutomatedDescription": "Automatically creates users, groups, and sudo permissions on host.",
"sshAuthDaemonLocation": "Auth Daemon Location",
"sshDaemonLocationSiteDescription": "Executes locally on the machine hosting the site connector.",
"sshDaemonLocationRemote": "On Remote Host",
"sshDaemonLocationRemoteDescription": "Executes on a separate target machine on the same network.",
"sshDaemonDisclaimer": "Ensure your target host is properly configured to run the auth daemon before completing this setup, or provisioning will fail.",
"sshDaemonPort": "Daemon Port",
"sshServerDestination": "Server Destination",
"sshServerDestinationDescription": "Configure the destination and port of the SSH server",
"destination": "Destination",
"bgTargetMultiSiteDisclaimer": "Selecting multiple sites enables resilient routing and failover for high availability.",
"roleAllowSsh": "Permitir SSH",
"roleAllowSshAllow": "Permitir",
"roleAllowSshDisallow": "Rechazar",
@@ -1957,7 +2087,7 @@
"sshSudoModeCommandsDescription": "El usuario sólo puede ejecutar los comandos especificados con sudo.",
"sshSudo": "Permitir sudo",
"sshSudoCommands": "Comandos Sudo",
"sshSudoCommandsDescription": "Lista separada por comas de comandos que el usuario puede ejecutar con sudo.",
"sshSudoCommandsDescription": "Comma separated list of commands the user is allowed to run with sudo. Absolute paths must be used.",
"sshCreateHomeDir": "Crear directorio principal",
"sshUnixGroups": "Grupos Unix",
"sshUnixGroupsDescription": "Grupos Unix separados por comas para agregar el usuario en el host de destino.",
@@ -2049,8 +2179,9 @@
"editInternalResourceDialogModeCidr": "CIDR",
"editInternalResourceDialogModeHttp": "HTTP",
"editInternalResourceDialogModeHttps": "HTTPS",
"editInternalResourceDialogModeSsh": "SSH",
"editInternalResourceDialogScheme": "Esquema",
"editInternalResourceDialogEnableSsl": "Activar TLS",
"editInternalResourceDialogEnableSsl": "Enable TLS",
"editInternalResourceDialogEnableSslDescription": "Habilitar cifrado SSL/TLS para conexiones HTTPS seguras al destino.",
"editInternalResourceDialogDestination": "Destino",
"editInternalResourceDialogDestinationHostDescription": "La dirección IP o nombre de host del recurso en la red del sitio.",
@@ -2098,9 +2229,10 @@
"createInternalResourceDialogModeCidr": "CIDR",
"createInternalResourceDialogModeHttp": "HTTP",
"createInternalResourceDialogModeHttps": "HTTPS",
"createInternalResourceDialogModeSsh": "SSH",
"scheme": "Esquema",
"createInternalResourceDialogScheme": "Esquema",
"createInternalResourceDialogEnableSsl": "Activar TLS",
"createInternalResourceDialogEnableSsl": "Enable TLS",
"createInternalResourceDialogEnableSslDescription": "Habilitar cifrado SSL/TLS para conexiones HTTPS seguras al destino.",
"createInternalResourceDialogDestination": "Destino",
"createInternalResourceDialogDestinationHostDescription": "La dirección IP o nombre de host del recurso en la red del sitio.",
@@ -2233,7 +2365,7 @@
"description": "Servidor Pangolin autoalojado más fiable y de bajo mantenimiento con campanas y silbidos extra",
"introTitle": "Pangolin autogestionado",
"introDescription": "es una opción de despliegue diseñada para personas que quieren simplicidad y fiabilidad extra mientras mantienen sus datos privados y autoalojados.",
"introDetail": "Con esta opción, todavía ejecuta su propio nodo Pangolin, sus túneles, terminación TLS y tráfico permanecen en su servidor. La diferencia es que la gestión y el control se gestionan a través de nuestro panel de control en la nube, que desbloquea una serie de ventajas:",
"introDetail": "With this option, you still run your own Pangolin node - your tunnels, TLS termination, and traffic all stay on your server. The difference is that management and monitoring are handled through our cloud dashboard, which unlocks a number of benefits:",
"benefitSimplerOperations": {
"title": "Operaciones simples",
"description": "No necesitas ejecutar tu propio servidor de correo o configurar alertas complejas. Recibirás cheques de salud y alertas de tiempo de inactividad."
@@ -2937,7 +3069,7 @@
"learnMore": "Más información",
"backToHome": "Volver a inicio",
"needToSignInToOrg": "¿Necesita usar el proveedor de identidad de su organización?",
"maintenanceMode": "Modo de mantenimiento",
"maintenanceMode": "Maintenance Page",
"maintenanceModeDescription": "Muestra una página de mantenimiento a los visitantes",
"maintenanceModeType": "Tipo de modo de mantenimiento",
"showMaintenancePage": "Mostrar página de mantenimiento a los visitantes",
@@ -2967,6 +3099,7 @@
"maintenanceScreenEstimatedCompletion": "Estimado completado:",
"createInternalResourceDialogDestinationRequired": "Se requiere destino",
"available": "Disponible",
"disabledResourceDescription": "When disabled, the resource will be inaccessible by everyone.",
"archived": "Archivado",
"noArchivedDevices": "No se encontraron dispositivos archivados",
"deviceArchived": "Dispositivo archivado",
@@ -3296,5 +3429,6 @@
"memberPortalResourceDisabled": "Recurso Deshabilitado",
"memberPortalShowingResources": "Mostrando {start}-{end} de {total} recursos",
"memberPortalPrevious": "Anterior",
"memberPortalNext": "Siguiente"
"memberPortalNext": "Siguiente",
"httpSettings": "HTTP Settings"
}

View File

@@ -176,6 +176,7 @@
"shareErrorCreateDescription": "Une erreur s'est produite lors de la création du lien partageable",
"shareCreateDescription": "N'importe qui avec ce lien peut accéder à la ressource",
"shareTitleOptional": "Titre (facultatif)",
"sharePathOptional": "Path (optional)",
"expireIn": "Expire dans",
"neverExpire": "N'expire jamais",
"shareExpireDescription": "Le délai 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.",
@@ -208,11 +209,33 @@
"resourcesSearch": "Chercher des ressources...",
"resourceAdd": "Ajouter une ressource",
"resourceErrorDelte": "Erreur lors de la de suppression de la ressource",
"resourcePoliciesTitle": "Manage Resource Policies",
"resourcePoliciesAttachedResourcesColumnTitle": "Attached resources",
"resourcePoliciesAttachedResources": "{count} resource(s)",
"resourcePoliciesAttachedResourcesEmpty": "no resources",
"resourcePoliciesDescription": "Create and manage authentication policies to control access to your resources",
"resourcePoliciesSearch": "Search policies...",
"resourcePoliciesAdd": "Add Policy",
"resourcePoliciesDefaultBadgeText": "Default policy",
"resourcePoliciesCreate": "Create Resource Policy",
"resourcePoliciesCreateDescription": "Follow the steps below to create a new policy",
"resourcePolicyName": "Policy Name",
"resourcePolicyNameDescription": "Give this policy a name to identify it across your resources",
"resourcePolicyNamePlaceholder": "e.g. Internal Access Policy",
"resourcePoliciesSeeAll": "See All Policies",
"resourcePolicyAuthMethodAdd": "Add Authentication Method",
"resourcePolicyOtpEmailAdd": "Add OTP emails",
"resourcePolicyRulesAdd": "Add Rules",
"resourcePolicyAuthMethodsDescription": "Allow access to resources via additional auth methods",
"resourcePolicyUsersRolesDescription": "Configure which users and roles can visit associated resources",
"rulesResourcePolicyDescription": "Configure rules to control access resources associated to this policy",
"authentication": "Authentification",
"protected": "Protégé",
"notProtected": "Non Protégé",
"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 retirer la ressource de l'organisation ?",
"resourcePolicyMessageRemove": "Once removed, the resource policy will no longer be accessible. All resources associated with the resource will be unlinked and left without authentication.",
"resourcePolicyQuestionRemove": "Are you sure you want to remove the resource policy from the organization?",
"resourceHTTP": "Ressource HTTPS",
"resourceHTTPDescription": "Proxy les demandes sur HTTPS en utilisant un nom de domaine entièrement qualifié.",
"resourceRaw": "Ressource TCP/UDP brute",
@@ -220,8 +243,9 @@
"resourceRawDescriptionCloud": "Requêtes de proxy sur TCP/UDP brute en utilisant un numéro de port. Nécessite des sites pour se connecter à un noeud distant.",
"resourceCreate": "Créer une ressource",
"resourceCreateDescription": "Suivez les étapes ci-dessous pour créer une nouvelle ressource",
"resourceCreateGeneralDescription": "Configure the basic resource settings including the name and the type",
"resourceSeeAll": "Voir toutes les ressources",
"resourceInfo": "Informations sur la ressource",
"resourceCreateGeneral": "General",
"resourceNameDescription": "Ceci est le nom d'affichage de la ressource.",
"siteSelect": "Sélectionnez un nœud",
"siteSearch": "Chercher un nœud",
@@ -231,12 +255,15 @@
"noCountryFound": "Aucun pays trouvé.",
"siteSelectionDescription": "Ce site fournira la connectivité à la cible.",
"resourceType": "Type de ressource",
"resourceTypeDescription": "Déterminer comment accéder à la ressource",
"resourceTypeDescription": "This controls the resource protocol and how it will be rendered in the browser. This cant be changed later.",
"resourceDomainDescription": "The resource will be served at this fully qualified domain name.",
"resourceHTTPSSettings": "Paramètres HTTPS",
"resourceHTTPSSettingsDescription": "Configurer comment la ressource sera accédée via HTTPS",
"resourcePortDescription": "The external port on the Pangolin instance or node where the resource will be accessible.",
"domainType": "Type de domaine",
"subdomain": "Sous-domaine",
"baseDomain": "Domaine racine",
"configure": "Configure",
"subdomnainDescription": "Le sous-domaine où la ressource sera accessible.",
"resourceRawSettings": "Paramètres TCP/UDP",
"resourceRawSettingsDescription": "Configurer comment la ressource sera accédée via TCP/UDP",
@@ -253,8 +280,27 @@
"resourceLearnRaw": "Apprenez à configurer les ressources TCP/UDP",
"resourceBack": "Retour aux ressources",
"resourceGoTo": "Aller à la ressource",
"resourcePolicyDelete": "Delete Resource Policy",
"resourcePolicyDeleteConfirm": "Confirm Delete Resource Policy",
"resourceDelete": "Supprimer la ressource",
"resourceDeleteConfirm": "Confirmer la suppression de la ressource",
"labelDelete": "Delete Label",
"labelAdd": "Add Label",
"labelCreateSuccessMessage": "Label Created Successfully",
"labelEditSuccessMessage": "Label Modified Successfully",
"labelNameField": "Label Name",
"labelColorField": "Label Color",
"labelPlaceholder": "Ex: homelab",
"labelCreate": "Create Label",
"createLabelDialogTitle": "Create Label",
"createLabelDialogDescription": "Create a new label that can be attached to this organization",
"labelEdit": "Edit Label",
"editLabelDialogTitle": "Update Label",
"editLabelDialogDescription": "Edit a new label that can be attached to this organization",
"labelDeleteConfirm": "Confirm Delete Label",
"labelErrorDelete": "Failed to delete label",
"labelMessageRemove": "This action is permanent. All sites, resources, and clients tagged with this label will be untagged.",
"labelQuestionRemove": "Are you sure you want to remove the label from the organization?",
"visibility": "Visibilité",
"enabled": "Activé",
"disabled": "Désactivé",
@@ -265,6 +311,8 @@
"rules": "Règles",
"resourceSettingDescription": "Configurer les paramètres de la ressource",
"resourceSetting": "Réglages de {resourceName}",
"resourcePolicySettingDescription": "Configure the settings on the resource policy",
"resourcePolicySetting": "{policyName} Settings",
"alwaysAllow": "Outrepasser l'authentification",
"alwaysDeny": "Bloquer l'accès",
"passToAuth": "Passer à l'authentification",
@@ -630,7 +678,7 @@
"createdAt": "Créé le",
"proxyErrorInvalidHeader": "Valeur d'en-tête Host personnalisée invalide. Utilisez le format de nom de domaine, ou laissez vide pour désactiver l'en-tête Host personnalisé.",
"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 TLS",
"proxyEnableSSL": "Enable TLS",
"proxyEnableSSLDescription": "Activer le cryptage SSL/TLS pour des connexions HTTPS sécurisées vers les cibles.",
"target": "Cible",
"configureTarget": "Configurer les cibles",
@@ -747,6 +795,16 @@
"rulesNoOne": "Aucune règle. Ajoutez une règle en utilisant le formulaire.",
"rulesOrder": "Les règles sont évaluées par priorité dans l'ordre croissant.",
"rulesSubmit": "Enregistrer les règles",
"policyErrorCreate": "Error creating policy",
"policyErrorCreateDescription": "An error occurred when creating the policy",
"policyErrorCreateMessageDescription": "An unexpected error occurred",
"policyErrorUpdate": "Error updating policy",
"policyErrorUpdateDescription": "An error occurred when updating the policy",
"policyErrorUpdateMessageDescription": "An unexpected error occurred",
"policyCreatedSuccess": "Resource policy succesfully created",
"policyUpdatedSuccess": "Resource policy succesfully updated",
"authMethodsSave": "Save auth methods",
"rulesSave": "Save Rules",
"resourceErrorCreate": "Erreur lors de la création de la ressource",
"resourceErrorCreateDescription": "Une erreur s'est produite lors de la création de la ressource",
"resourceErrorCreateMessage": "Erreur lors de la création de la ressource :",
@@ -810,6 +868,16 @@
"pincodeAdd": "Ajouter un code PIN",
"pincodeRemove": "Supprimer le code PIN",
"resourceAuthMethods": "Méthodes d'authentification",
"resourcePolicyAuthMethodsEmpty": "No authentication method",
"resourcePolicyOtpEmpty": "No one time password",
"resourcePolicyReadOnly": "This policy is Read only",
"resourcePolicyReadOnlyDescription": "This resource policy is shared accross multiple resources, you cannot edit it on this page.",
"resourcePolicyTypeSave": "Save Resource type",
"resourcePolicySelect": "Select resource policy",
"resourcePolicySelectError": "Select a resource policy",
"resourcePolicyNotFound": "Policy not found",
"resourcePolicySearch": "Search policies",
"resourcePolicyRulesEmpty": "No authentication rules",
"resourceAuthMethodsDescriptions": "Permettre l'accès à la ressource via des méthodes d'authentification supplémentaires",
"resourceAuthSettingsSave": "Enregistré avec succès",
"resourceAuthSettingsSaveDescription": "Les paramètres d'authentification ont été enregistrés",
@@ -845,6 +913,12 @@
"resourcePincodeSetupTitle": "Définir le code PIN",
"resourcePincodeSetupTitleDescription": "Définir un code PIN pour protéger cette ressource",
"resourceRoleDescription": "Les administrateurs peuvent toujours accéder à cette ressource.",
"resourcePolicySelectTitle": "Resource Access Policy",
"resourcePolicySelectDescription": "Select the resource policy type for authentication",
"resourcePolicyInline": "Inline Resource Policy",
"resourcePolicyInlineDescription": "Access Policy scoped to only this resource",
"resourcePolicyShared": "Shared Resource Policy",
"resourcePolicySharedDescription": "Access Policy shared accross multiple resources",
"resourceUsersRoles": "Contrôles d'accès",
"resourceUsersRolesDescription": "Configurer quels utilisateurs et rôles peuvent visiter cette ressource",
"resourceUsersRolesSubmit": "Enregistrer les contrôles d'accès",
@@ -1140,6 +1214,18 @@
"idpErrorConnectingTo": "Un problème est survenu lors de la connexion à {name}. Veuillez contacter votre administrateur.",
"idpErrorNotFound": "IdP introuvable",
"inviteInvalid": "Invitation invalide",
"labels": "Labels",
"orgLabelsDescription": "Manage labels in this organization.",
"addLabels": "Add labels",
"siteLabelsTab": "Labels",
"siteLabelsDescription": "Manage labels associated with this site.",
"labelsNotFound": "Labels not found",
"labelSearch": "Search labels",
"accessLabelFilterCount": "{count, plural, one {# label} other {# labels}}",
"labelOverflowCount": "+{count, plural, one {# label} other {# labels}}",
"accessLabelFilterClear": "Clear label filters",
"selectColor": "Select color",
"createNewLabel": "Create new org label \"{label}\"",
"inviteInvalidDescription": "Le lien d'invitation n'est pas valide.",
"inviteErrorWrongUser": "L'invitation n'est pas pour cet utilisateur",
"inviteErrorUserNotExists": "L'utilisateur n'existe pas. Veuillez d'abord créer un compte.",
@@ -1374,6 +1460,8 @@
"sidebarResources": "Ressource",
"sidebarProxyResources": "Publique",
"sidebarClientResources": "Privé",
"sidebarPolicies": "Policies",
"sidebarResourcePolicies": "Resources",
"sidebarAccessControl": "Contrôle d'accès",
"sidebarLogsAndAnalytics": "Journaux & Analytiques",
"sidebarTeam": "Equipe",
@@ -1557,7 +1645,8 @@
"standaloneHcFilterSiteIdFallback": "Site {id}",
"standaloneHcFilterResourceIdFallback": "Ressource {id}",
"blueprints": "Configs",
"blueprintsDescription": "Appliquer les configurations déclaratives et afficher les exécutions précédentes",
"blueprintsLog": "Blueprints Log",
"blueprintsDescription": "View past blueprint applications and their results",
"blueprintAdd": "Ajouter une Config",
"blueprintGoBack": "Voir toutes les Configs",
"blueprintCreate": "Créer une Config",
@@ -1575,7 +1664,17 @@
"contents": "Contenus",
"parsedContents": "Contenu analysé (lecture seule)",
"enableDockerSocket": "Activer la Config Docker",
"enableDockerSocketDescription": "Activer le ramassage d'étiquettes de socket Docker pour les étiquettes de plan. Le chemin de socket doit être fourni à Newt.",
"enableDockerSocketDescription": "Enable Docker Socket label scraping for blueprint labels. Socket path must be provided to the site connector. Read about how this works in <docsLink>the documentation</docsLink>.",
"newtAutoUpdate": "Enable Site Auto-Update",
"newtAutoUpdateDescription": "When enabled, site connectors will automatically update to the latest version when a new release is available.",
"siteAutoUpdate": "Site Auto-Update",
"siteAutoUpdateLabel": "Enable Auto-Update",
"siteAutoUpdateDescription": "Control whether this site's connector automatically downloads the latest version.",
"siteAutoUpdateOrgDefault": "Organization default: {state}",
"siteAutoUpdateOverriding": "Overriding organization setting",
"siteAutoUpdateResetToOrg": "Reset to Organization Default",
"siteAutoUpdateEnabled": "enabled",
"siteAutoUpdateDisabled": "disabled",
"viewDockerContainers": "Voir les conteneurs Docker",
"containersIn": "Conteneurs en {siteName}",
"selectContainerDescription": "Sélectionnez n'importe quel conteneur à utiliser comme nom d'hôte pour cette cible. Cliquez sur un port pour utiliser un port.",
@@ -1620,6 +1719,7 @@
"certificateStatus": "Certificat",
"certificateStatusAutoRefreshHint": "L'état se rafraîchit automatiquement.",
"loading": "Chargement",
"loadingEllipsis": "Loading...",
"loadingAnalytics": "Chargement de l'analyse",
"restart": "Redémarrer",
"domains": "Domaines",
@@ -1846,6 +1946,7 @@
"billingManageLicenseSubscription": "Gérer votre abonnement pour les clés de licence auto-hébergées payantes",
"billingCurrentKeys": "Clés actuelles",
"billingModifyCurrentPlan": "Modifier le plan actuel",
"billingManageLicenseSubscriptionDescription": "Manage your subscription for paid self-hosted license keys and download invoices.",
"billingConfirmUpgrade": "Confirmer la mise à niveau",
"billingConfirmDowngrade": "Confirmer la rétrogradation",
"billingConfirmUpgradeDescription": "Vous êtes sur le point de mettre à niveau votre offre. Examinez les nouvelles limites et les nouveaux prix ci-dessous.",
@@ -1943,7 +2044,36 @@
"timeIsInSeconds": "Le temps est exprimé en secondes",
"requireDeviceApproval": "Exiger les autorisations de l'appareil",
"requireDeviceApprovalDescription": "Les utilisateurs ayant ce rôle ont besoin de nouveaux périphériques approuvés par un administrateur avant de pouvoir se connecter et accéder aux ressources.",
"sshAccess": "Accès SSH",
"sshSettings": "SSH Settings",
"rdpSettings": "RDP Settings",
"vncSettings": "VNC Settings",
"sshServer": "SSH Server",
"rdpServer": "RDP Server",
"vncServer": "VNC Server",
"sshServerDescription": "Set up the authentication method, daemon location, and server destination",
"rdpServerDescription": "Configure the destination and port of the RDP server",
"vncServerDescription": "Configure the destination and port of the VNC server",
"sshServerMode": "Mode",
"sshServerModeStandard": "Standard SSH Server",
"sshServerModePangolin": "Pangolin SSH",
"sshServerModeStandardDescription": "Routes commands over network to an SSH server such as OpenSSH.",
"sshServerModeNative": "Native SSH Server",
"sshServerModeNativeDescription": "Executes commands directly on the host via the Site Connector. No network config required.",
"sshAuthenticationMethod": "Authentication Method",
"sshAuthMethodManual": "Manual Authentication",
"sshAuthMethodManualDescription": "Requires existing host credentials. Bypasses automatic provisioning.",
"sshAuthMethodAutomated": "Automated Provisioning",
"sshAuthMethodAutomatedDescription": "Automatically creates users, groups, and sudo permissions on host.",
"sshAuthDaemonLocation": "Auth Daemon Location",
"sshDaemonLocationSiteDescription": "Executes locally on the machine hosting the site connector.",
"sshDaemonLocationRemote": "On Remote Host",
"sshDaemonLocationRemoteDescription": "Executes on a separate target machine on the same network.",
"sshDaemonDisclaimer": "Ensure your target host is properly configured to run the auth daemon before completing this setup, or provisioning will fail.",
"sshDaemonPort": "Daemon Port",
"sshServerDestination": "Server Destination",
"sshServerDestinationDescription": "Configure the destination and port of the SSH server",
"destination": "Destination",
"bgTargetMultiSiteDisclaimer": "Selecting multiple sites enables resilient routing and failover for high availability.",
"roleAllowSsh": "Autoriser SSH",
"roleAllowSshAllow": "Autoriser",
"roleAllowSshDisallow": "Interdire",
@@ -1957,7 +2087,7 @@
"sshSudoModeCommandsDescription": "L'utilisateur ne peut exécuter que les commandes spécifiées avec sudo.",
"sshSudo": "Autoriser sudo",
"sshSudoCommands": "Commandes Sudo",
"sshSudoCommandsDescription": "Liste des commandes séparées par des virgules que l'utilisateur est autorisé à exécuter avec sudo.",
"sshSudoCommandsDescription": "Comma separated list of commands the user is allowed to run with sudo. Absolute paths must be used.",
"sshCreateHomeDir": "Créer un répertoire personnel",
"sshUnixGroups": "Groupes Unix",
"sshUnixGroupsDescription": "Groupes Unix séparés par des virgules pour ajouter l'utilisateur sur l'hôte cible.",
@@ -2049,8 +2179,9 @@
"editInternalResourceDialogModeCidr": "CIDR",
"editInternalResourceDialogModeHttp": "HTTP",
"editInternalResourceDialogModeHttps": "HTTPS",
"editInternalResourceDialogModeSsh": "SSH",
"editInternalResourceDialogScheme": "Méthode HTTP",
"editInternalResourceDialogEnableSsl": "Activer TLS",
"editInternalResourceDialogEnableSsl": "Enable TLS",
"editInternalResourceDialogEnableSslDescription": "Activer le cryptage SSL/TLS pour des connexions HTTPS sécurisées vers la destination.",
"editInternalResourceDialogDestination": "Destination",
"editInternalResourceDialogDestinationHostDescription": "L'adresse IP ou le nom d'hôte de la ressource sur le réseau du site.",
@@ -2098,9 +2229,10 @@
"createInternalResourceDialogModeCidr": "CIDR",
"createInternalResourceDialogModeHttp": "HTTP",
"createInternalResourceDialogModeHttps": "HTTPS",
"createInternalResourceDialogModeSsh": "SSH",
"scheme": "Méthode HTTP",
"createInternalResourceDialogScheme": "Méthode HTTP",
"createInternalResourceDialogEnableSsl": "Activer TLS",
"createInternalResourceDialogEnableSsl": "Enable TLS",
"createInternalResourceDialogEnableSslDescription": "Activer le cryptage SSL/TLS pour des connexions HTTPS sécurisées vers la destination.",
"createInternalResourceDialogDestination": "Destination",
"createInternalResourceDialogDestinationHostDescription": "L'adresse IP ou le nom d'hôte de la ressource sur le réseau du site.",
@@ -2233,7 +2365,7 @@
"description": "Serveur Pangolin auto-hébergé avec des cloches et des sifflets supplémentaires",
"introTitle": "Pangolin auto-hébergé géré",
"introDescription": "est une option de déploiement conçue pour les personnes qui veulent de la simplicité et de la fiabilité tout en gardant leurs données privées et auto-hébergées.",
"introDetail": "Avec cette option, vous exécutez toujours votre propre nœud Pangolin - vos tunnels, la terminaison TLS et le trafic restent sur votre serveur. La différence est que la gestion et la surveillance sont gérées via notre tableau de bord du cloud, qui déverrouille un certain nombre d'avantages :",
"introDetail": "With this option, you still run your own Pangolin node - your tunnels, TLS termination, and traffic all stay on your server. The difference is that management and monitoring are handled through our cloud dashboard, which unlocks a number of benefits:",
"benefitSimplerOperations": {
"title": "Opérations plus simples",
"description": "Pas besoin de faire tourner votre propre serveur de messagerie ou de configurer des alertes complexes. Vous obtiendrez des contrôles de santé et des alertes de temps d'arrêt par la suite."
@@ -2937,7 +3069,7 @@
"learnMore": "En savoir plus",
"backToHome": "Retour à l'accueil",
"needToSignInToOrg": "Besoin d'utiliser le fournisseur d'identité de votre organisation ?",
"maintenanceMode": "Mode de maintenance",
"maintenanceMode": "Maintenance Page",
"maintenanceModeDescription": "Afficher une page de maintenance aux visiteurs",
"maintenanceModeType": "Type de mode de maintenance",
"showMaintenancePage": "Afficher une page de maintenance aux visiteurs",
@@ -2967,6 +3099,7 @@
"maintenanceScreenEstimatedCompletion": "Achèvement estimé :",
"createInternalResourceDialogDestinationRequired": "La destination est requise",
"available": "Disponible",
"disabledResourceDescription": "When disabled, the resource will be inaccessible by everyone.",
"archived": "Archivé",
"noArchivedDevices": "Aucun périphérique archivé trouvé",
"deviceArchived": "Appareil archivé",
@@ -3296,5 +3429,6 @@
"memberPortalResourceDisabled": "Ressource désactivée",
"memberPortalShowingResources": "Affichage de {start}-{end} sur {total} ressources",
"memberPortalPrevious": "Précédent",
"memberPortalNext": "Suivant"
"memberPortalNext": "Suivant",
"httpSettings": "HTTP Settings"
}

View File

@@ -176,6 +176,7 @@
"shareErrorCreateDescription": "Si è verificato un errore durante la creazione del link di condivisione",
"shareCreateDescription": "Chiunque con questo link può accedere alla risorsa",
"shareTitleOptional": "Titolo (facoltativo)",
"sharePathOptional": "Path (optional)",
"expireIn": "Scadenza In",
"neverExpire": "Nessuna scadenza",
"shareExpireDescription": "Il tempo di scadenza indica 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.",
@@ -208,11 +209,33 @@
"resourcesSearch": "Cerca risorse...",
"resourceAdd": "Aggiungi Risorsa",
"resourceErrorDelte": "Errore nell'eliminare la risorsa",
"resourcePoliciesTitle": "Manage Resource Policies",
"resourcePoliciesAttachedResourcesColumnTitle": "Attached resources",
"resourcePoliciesAttachedResources": "{count} resource(s)",
"resourcePoliciesAttachedResourcesEmpty": "no resources",
"resourcePoliciesDescription": "Create and manage authentication policies to control access to your resources",
"resourcePoliciesSearch": "Search policies...",
"resourcePoliciesAdd": "Add Policy",
"resourcePoliciesDefaultBadgeText": "Default policy",
"resourcePoliciesCreate": "Create Resource Policy",
"resourcePoliciesCreateDescription": "Follow the steps below to create a new policy",
"resourcePolicyName": "Policy Name",
"resourcePolicyNameDescription": "Give this policy a name to identify it across your resources",
"resourcePolicyNamePlaceholder": "e.g. Internal Access Policy",
"resourcePoliciesSeeAll": "See All Policies",
"resourcePolicyAuthMethodAdd": "Add Authentication Method",
"resourcePolicyOtpEmailAdd": "Add OTP emails",
"resourcePolicyRulesAdd": "Add Rules",
"resourcePolicyAuthMethodsDescription": "Allow access to resources via additional auth methods",
"resourcePolicyUsersRolesDescription": "Configure which users and roles can visit associated resources",
"rulesResourcePolicyDescription": "Configure rules to control access resources associated to this policy",
"authentication": "Autenticazione",
"protected": "Protetto",
"notProtected": "Non Protetto",
"resourceMessageRemove": "Una volta rimossa la risorsa non sarà più accessibile. Tutti gli oggetti target associati alla risorsa saranno rimossi.",
"resourceQuestionRemove": "Sei sicuro di voler rimuovere la risorsa dall'organizzazione?",
"resourcePolicyMessageRemove": "Once removed, the resource policy will no longer be accessible. All resources associated with the resource will be unlinked and left without authentication.",
"resourcePolicyQuestionRemove": "Are you sure you want to remove the resource policy from the organization?",
"resourceHTTP": "Risorsa HTTPS",
"resourceHTTPDescription": "Richieste proxy su HTTPS usando un nome di dominio completo.",
"resourceRaw": "Risorsa Raw TCP/UDP",
@@ -220,8 +243,9 @@
"resourceRawDescriptionCloud": "Richiesta proxy su TCP/UDP grezzo utilizzando un numero di porta. Richiede siti per connettersi a un nodo remoto.",
"resourceCreate": "Crea Risorsa",
"resourceCreateDescription": "Segui i passaggi seguenti per creare una nuova risorsa",
"resourceCreateGeneralDescription": "Configure the basic resource settings including the name and the type",
"resourceSeeAll": "Vedi Tutte Le Risorse",
"resourceInfo": "Informazioni Risorsa",
"resourceCreateGeneral": "General",
"resourceNameDescription": "Questo è il nome visualizzato per la risorsa.",
"siteSelect": "Seleziona sito",
"siteSearch": "Cerca sito",
@@ -231,12 +255,15 @@
"noCountryFound": "Nessun paese trovato.",
"siteSelectionDescription": "Questo sito fornirà connettività all'oggetto target.",
"resourceType": "Tipo Di Risorsa",
"resourceTypeDescription": "Determinare come accedere alla risorsa",
"resourceTypeDescription": "This controls the resource protocol and how it will be rendered in the browser. This cant be changed later.",
"resourceDomainDescription": "The resource will be served at this fully qualified domain name.",
"resourceHTTPSSettings": "Impostazioni HTTPS",
"resourceHTTPSSettingsDescription": "Configura come sarà possibile accedere alla risorsa su HTTPS",
"resourcePortDescription": "The external port on the Pangolin instance or node where the resource will be accessible.",
"domainType": "Tipo Di Dominio",
"subdomain": "Sottodominio",
"baseDomain": "Dominio Base",
"configure": "Configure",
"subdomnainDescription": "Il sottodominio in cui la risorsa sarà accessibile.",
"resourceRawSettings": "Impostazioni TCP/UDP",
"resourceRawSettingsDescription": "Configura come accedere alla risorsa tramite TCP/UDP",
@@ -253,8 +280,27 @@
"resourceLearnRaw": "Scopri come configurare le risorse TCP/UDP",
"resourceBack": "Torna alle risorse",
"resourceGoTo": "Vai alla Risorsa",
"resourcePolicyDelete": "Delete Resource Policy",
"resourcePolicyDeleteConfirm": "Confirm Delete Resource Policy",
"resourceDelete": "Elimina Risorsa",
"resourceDeleteConfirm": "Conferma Eliminazione Risorsa",
"labelDelete": "Delete Label",
"labelAdd": "Add Label",
"labelCreateSuccessMessage": "Label Created Successfully",
"labelEditSuccessMessage": "Label Modified Successfully",
"labelNameField": "Label Name",
"labelColorField": "Label Color",
"labelPlaceholder": "Ex: homelab",
"labelCreate": "Create Label",
"createLabelDialogTitle": "Create Label",
"createLabelDialogDescription": "Create a new label that can be attached to this organization",
"labelEdit": "Edit Label",
"editLabelDialogTitle": "Update Label",
"editLabelDialogDescription": "Edit a new label that can be attached to this organization",
"labelDeleteConfirm": "Confirm Delete Label",
"labelErrorDelete": "Failed to delete label",
"labelMessageRemove": "This action is permanent. All sites, resources, and clients tagged with this label will be untagged.",
"labelQuestionRemove": "Are you sure you want to remove the label from the organization?",
"visibility": "Visibilità",
"enabled": "Abilitato",
"disabled": "Disabilitato",
@@ -265,6 +311,8 @@
"rules": "Regole",
"resourceSettingDescription": "Configura le impostazioni sulla risorsa",
"resourceSetting": "Impostazioni {resourceName}",
"resourcePolicySettingDescription": "Configure the settings on the resource policy",
"resourcePolicySetting": "{policyName} Settings",
"alwaysAllow": "Bypass Autenticazione",
"alwaysDeny": "Blocca Accesso",
"passToAuth": "Passa all'autenticazione",
@@ -630,7 +678,7 @@
"createdAt": "Creato Il",
"proxyErrorInvalidHeader": "Valore dell'intestazione Host personalizzata non valido. Usa il formato nome dominio o salva vuoto per rimuovere l'intestazione Host personalizzata.",
"proxyErrorTls": "Nome Server TLS non valido. Usa il formato nome dominio o salva vuoto per rimuovere il Nome Server TLS.",
"proxyEnableSSL": "Abilita TLS",
"proxyEnableSSL": "Enable TLS",
"proxyEnableSSLDescription": "Abilita la crittografia SSL/TLS per connessioni HTTPS sicure alle risorse interne target.",
"target": "Target",
"configureTarget": "Configura Risorse Interne",
@@ -747,6 +795,16 @@
"rulesNoOne": "Nessuna regola. Aggiungi una regola usando il modulo.",
"rulesOrder": "Le regole sono valutate per priorità in ordine crescente.",
"rulesSubmit": "Salva Regole",
"policyErrorCreate": "Error creating policy",
"policyErrorCreateDescription": "An error occurred when creating the policy",
"policyErrorCreateMessageDescription": "An unexpected error occurred",
"policyErrorUpdate": "Error updating policy",
"policyErrorUpdateDescription": "An error occurred when updating the policy",
"policyErrorUpdateMessageDescription": "An unexpected error occurred",
"policyCreatedSuccess": "Resource policy succesfully created",
"policyUpdatedSuccess": "Resource policy succesfully updated",
"authMethodsSave": "Save auth methods",
"rulesSave": "Save Rules",
"resourceErrorCreate": "Errore nella creazione della risorsa",
"resourceErrorCreateDescription": "Si è verificato un errore durante la creazione della risorsa",
"resourceErrorCreateMessage": "Errore nella creazione della risorsa:",
@@ -810,6 +868,16 @@
"pincodeAdd": "Aggiungi Codice PIN",
"pincodeRemove": "Rimuovi Codice PIN",
"resourceAuthMethods": "Metodi di Autenticazione",
"resourcePolicyAuthMethodsEmpty": "No authentication method",
"resourcePolicyOtpEmpty": "No one time password",
"resourcePolicyReadOnly": "This policy is Read only",
"resourcePolicyReadOnlyDescription": "This resource policy is shared accross multiple resources, you cannot edit it on this page.",
"resourcePolicyTypeSave": "Save Resource type",
"resourcePolicySelect": "Select resource policy",
"resourcePolicySelectError": "Select a resource policy",
"resourcePolicyNotFound": "Policy not found",
"resourcePolicySearch": "Search policies",
"resourcePolicyRulesEmpty": "No authentication rules",
"resourceAuthMethodsDescriptions": "Consenti l'accesso alla risorsa tramite metodi di autenticazione aggiuntivi",
"resourceAuthSettingsSave": "Salvato con successo",
"resourceAuthSettingsSaveDescription": "Le impostazioni di autenticazione sono state salvate",
@@ -845,6 +913,12 @@
"resourcePincodeSetupTitle": "Imposta Codice PIN",
"resourcePincodeSetupTitleDescription": "Imposta un codice PIN per proteggere questa risorsa",
"resourceRoleDescription": "Gli amministratori possono sempre accedere a questa risorsa.",
"resourcePolicySelectTitle": "Resource Access Policy",
"resourcePolicySelectDescription": "Select the resource policy type for authentication",
"resourcePolicyInline": "Inline Resource Policy",
"resourcePolicyInlineDescription": "Access Policy scoped to only this resource",
"resourcePolicyShared": "Shared Resource Policy",
"resourcePolicySharedDescription": "Access Policy shared accross multiple resources",
"resourceUsersRoles": "Controlli di Accesso",
"resourceUsersRolesDescription": "Configura quali utenti e ruoli possono visitare questa risorsa",
"resourceUsersRolesSubmit": "Salva Controlli di Accesso",
@@ -1140,6 +1214,18 @@
"idpErrorConnectingTo": "Si è verificato un problema durante la connessione a {name}. Contatta il tuo amministratore.",
"idpErrorNotFound": "IdP non trovato",
"inviteInvalid": "Invito Non Valido",
"labels": "Labels",
"orgLabelsDescription": "Manage labels in this organization.",
"addLabels": "Add labels",
"siteLabelsTab": "Labels",
"siteLabelsDescription": "Manage labels associated with this site.",
"labelsNotFound": "Labels not found",
"labelSearch": "Search labels",
"accessLabelFilterCount": "{count, plural, one {# label} other {# labels}}",
"labelOverflowCount": "+{count, plural, one {# label} other {# labels}}",
"accessLabelFilterClear": "Clear label filters",
"selectColor": "Select color",
"createNewLabel": "Create new org label \"{label}\"",
"inviteInvalidDescription": "Il link di invito non è valido.",
"inviteErrorWrongUser": "L'invito non è per questo utente",
"inviteErrorUserNotExists": "L'utente non esiste. Si prega di creare prima un account.",
@@ -1374,6 +1460,8 @@
"sidebarResources": "Risorse",
"sidebarProxyResources": "Pubblico",
"sidebarClientResources": "Privato",
"sidebarPolicies": "Policies",
"sidebarResourcePolicies": "Resources",
"sidebarAccessControl": "Controllo Accesso",
"sidebarLogsAndAnalytics": "Registri E Analisi",
"sidebarTeam": "Squadra",
@@ -1557,7 +1645,8 @@
"standaloneHcFilterSiteIdFallback": "Sito {id}",
"standaloneHcFilterResourceIdFallback": "Risorsa {id}",
"blueprints": "Progetti",
"blueprintsDescription": "Applica le configurazioni dichiarative e visualizza le partite precedenti",
"blueprintsLog": "Blueprints Log",
"blueprintsDescription": "View past blueprint applications and their results",
"blueprintAdd": "Aggiungi Progetto",
"blueprintGoBack": "Vedi tutti i progetti",
"blueprintCreate": "Crea Progetto",
@@ -1575,7 +1664,17 @@
"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.",
"enableDockerSocketDescription": "Enable Docker Socket label scraping for blueprint labels. Socket path must be provided to the site connector. Read about how this works in <docsLink>the documentation</docsLink>.",
"newtAutoUpdate": "Enable Site Auto-Update",
"newtAutoUpdateDescription": "When enabled, site connectors will automatically update to the latest version when a new release is available.",
"siteAutoUpdate": "Site Auto-Update",
"siteAutoUpdateLabel": "Enable Auto-Update",
"siteAutoUpdateDescription": "Control whether this site's connector automatically downloads the latest version.",
"siteAutoUpdateOrgDefault": "Organization default: {state}",
"siteAutoUpdateOverriding": "Overriding organization setting",
"siteAutoUpdateResetToOrg": "Reset to Organization Default",
"siteAutoUpdateEnabled": "enabled",
"siteAutoUpdateDisabled": "disabled",
"viewDockerContainers": "Visualizza Contenitori Docker",
"containersIn": "Contenitori in {siteName}",
"selectContainerDescription": "Seleziona qualsiasi contenitore da usare come hostname per questo obiettivo. Fai clic su una porta per usare una porta.",
@@ -1620,6 +1719,7 @@
"certificateStatus": "Certificato",
"certificateStatusAutoRefreshHint": "Lo stato si aggiorna automaticamente.",
"loading": "Caricamento",
"loadingEllipsis": "Loading...",
"loadingAnalytics": "Caricamento Delle Analisi",
"restart": "Riavvia",
"domains": "Domini",
@@ -1846,6 +1946,7 @@
"billingManageLicenseSubscription": "Gestisci il tuo abbonamento per le chiavi di licenza self-hosted a pagamento",
"billingCurrentKeys": "Tasti Attuali",
"billingModifyCurrentPlan": "Modifica Il Piano Corrente",
"billingManageLicenseSubscriptionDescription": "Manage your subscription for paid self-hosted license keys and download invoices.",
"billingConfirmUpgrade": "Conferma Aggiornamento",
"billingConfirmDowngrade": "Conferma Downgrade",
"billingConfirmUpgradeDescription": "Stai per aggiornare il tuo piano. Controlla i nuovi limiti e prezzi qui sotto.",
@@ -1943,7 +2044,36 @@
"timeIsInSeconds": "Il tempo è in secondi",
"requireDeviceApproval": "Richiede Approvazioni Dispositivo",
"requireDeviceApprovalDescription": "Gli utenti con questo ruolo hanno bisogno di nuovi dispositivi approvati da un amministratore prima di poter connettersi e accedere alle risorse.",
"sshAccess": "Accesso SSH",
"sshSettings": "SSH Settings",
"rdpSettings": "RDP Settings",
"vncSettings": "VNC Settings",
"sshServer": "SSH Server",
"rdpServer": "RDP Server",
"vncServer": "VNC Server",
"sshServerDescription": "Set up the authentication method, daemon location, and server destination",
"rdpServerDescription": "Configure the destination and port of the RDP server",
"vncServerDescription": "Configure the destination and port of the VNC server",
"sshServerMode": "Mode",
"sshServerModeStandard": "Standard SSH Server",
"sshServerModePangolin": "Pangolin SSH",
"sshServerModeStandardDescription": "Routes commands over network to an SSH server such as OpenSSH.",
"sshServerModeNative": "Native SSH Server",
"sshServerModeNativeDescription": "Executes commands directly on the host via the Site Connector. No network config required.",
"sshAuthenticationMethod": "Authentication Method",
"sshAuthMethodManual": "Manual Authentication",
"sshAuthMethodManualDescription": "Requires existing host credentials. Bypasses automatic provisioning.",
"sshAuthMethodAutomated": "Automated Provisioning",
"sshAuthMethodAutomatedDescription": "Automatically creates users, groups, and sudo permissions on host.",
"sshAuthDaemonLocation": "Auth Daemon Location",
"sshDaemonLocationSiteDescription": "Executes locally on the machine hosting the site connector.",
"sshDaemonLocationRemote": "On Remote Host",
"sshDaemonLocationRemoteDescription": "Executes on a separate target machine on the same network.",
"sshDaemonDisclaimer": "Ensure your target host is properly configured to run the auth daemon before completing this setup, or provisioning will fail.",
"sshDaemonPort": "Daemon Port",
"sshServerDestination": "Server Destination",
"sshServerDestinationDescription": "Configure the destination and port of the SSH server",
"destination": "Destination",
"bgTargetMultiSiteDisclaimer": "Selecting multiple sites enables resilient routing and failover for high availability.",
"roleAllowSsh": "Consenti SSH",
"roleAllowSshAllow": "Consenti",
"roleAllowSshDisallow": "Non Consentire",
@@ -1957,7 +2087,7 @@
"sshSudoModeCommandsDescription": "L'utente può eseguire solo i comandi specificati con sudo.",
"sshSudo": "Consenti sudo",
"sshSudoCommands": "Comandi Sudo",
"sshSudoCommandsDescription": "Elenco di comandi separati da virgole che l'utente può eseguire con sudo.",
"sshSudoCommandsDescription": "Comma separated list of commands the user is allowed to run with sudo. Absolute paths must be used.",
"sshCreateHomeDir": "Crea Cartella Home",
"sshUnixGroups": "Gruppi Unix",
"sshUnixGroupsDescription": "Gruppi Unix separati da virgole per aggiungere l'utente sull'host di destinazione.",
@@ -2049,8 +2179,9 @@
"editInternalResourceDialogModeCidr": "CIDR",
"editInternalResourceDialogModeHttp": "HTTP",
"editInternalResourceDialogModeHttps": "HTTPS",
"editInternalResourceDialogModeSsh": "SSH",
"editInternalResourceDialogScheme": "Metodo HTTP",
"editInternalResourceDialogEnableSsl": "Abilitare TLS",
"editInternalResourceDialogEnableSsl": "Enable TLS",
"editInternalResourceDialogEnableSslDescription": "Abilita la crittografia SSL/TLS per connessioni HTTPS sicure alla destinazione.",
"editInternalResourceDialogDestination": "Destinazione",
"editInternalResourceDialogDestinationHostDescription": "L'indirizzo IP o il nome host della risorsa nella rete del sito.",
@@ -2098,9 +2229,10 @@
"createInternalResourceDialogModeCidr": "CIDR",
"createInternalResourceDialogModeHttp": "HTTP",
"createInternalResourceDialogModeHttps": "HTTPS",
"createInternalResourceDialogModeSsh": "SSH",
"scheme": "Metodo HTTP",
"createInternalResourceDialogScheme": "Metodo HTTP",
"createInternalResourceDialogEnableSsl": "Abilitare TLS",
"createInternalResourceDialogEnableSsl": "Enable TLS",
"createInternalResourceDialogEnableSslDescription": "Abilita la crittografia SSL/TLS per connessioni HTTPS sicure alla destinazione.",
"createInternalResourceDialogDestination": "Destinazione",
"createInternalResourceDialogDestinationHostDescription": "L'indirizzo IP o il nome host della risorsa nella rete del sito.",
@@ -2233,7 +2365,7 @@
"description": "Server Pangolin self-hosted più affidabile e a bassa manutenzione con campanelli e fischietti extra",
"introTitle": "Managed Self-Hosted Pangolin",
"introDescription": "è un'opzione di distribuzione progettata per le persone che vogliono la semplicità e l'affidabilità extra mantenendo i loro dati privati e self-hosted.",
"introDetail": "Con questa opzione, esegui ancora il tuo nodo Pangolin - i tunnel, la terminazione TLS e il traffico rimangono tutti sul tuo server. La differenza è che la gestione e il monitoraggio sono gestiti attraverso il nostro cruscotto cloud, che sblocca una serie di vantaggi:",
"introDetail": "With this option, you still run your own Pangolin node - your tunnels, TLS termination, and traffic all stay on your server. The difference is that management and monitoring are handled through our cloud dashboard, which unlocks a number of benefits:",
"benefitSimplerOperations": {
"title": "Operazioni più semplici",
"description": "Non è necessario eseguire il proprio server di posta o impostare un avviso complesso. Otterrai controlli di salute e avvisi di inattività fuori dalla casella."
@@ -2937,7 +3069,7 @@
"learnMore": "Scopri di più",
"backToHome": "Torna alla home",
"needToSignInToOrg": "Hai bisogno di utilizzare il provider di identità della tua organizzazione?",
"maintenanceMode": "Modalità di Manutenzione",
"maintenanceMode": "Maintenance Page",
"maintenanceModeDescription": "Visualizza una pagina di manutenzione ai visitatori",
"maintenanceModeType": "Tipo di Modalità di Manutenzione",
"showMaintenancePage": "Mostra una pagina di manutenzione ai visitatori",
@@ -2967,6 +3099,7 @@
"maintenanceScreenEstimatedCompletion": "Completamento Stimato:",
"createInternalResourceDialogDestinationRequired": "Destinazione richiesta",
"available": "Disponibile",
"disabledResourceDescription": "When disabled, the resource will be inaccessible by everyone.",
"archived": "Archiviato",
"noArchivedDevices": "Nessun dispositivo archiviato trovato",
"deviceArchived": "Dispositivo archiviato",
@@ -3296,5 +3429,6 @@
"memberPortalResourceDisabled": "Risorsa Disabilitata",
"memberPortalShowingResources": "Mostrando {start}-{end} di {total} risorse",
"memberPortalPrevious": "Precedente",
"memberPortalNext": "Successivo"
"memberPortalNext": "Successivo",
"httpSettings": "HTTP Settings"
}

View File

@@ -176,6 +176,7 @@
"shareErrorCreateDescription": "공유 링크를 생성하는 동안 오류가 발생했습니다",
"shareCreateDescription": "이 링크가 있는 누구나 리소스에 접근할 수 있습니다.",
"shareTitleOptional": "제목 (선택 사항)",
"sharePathOptional": "Path (optional)",
"expireIn": "만료됨",
"neverExpire": "만료되지 않음",
"shareExpireDescription": "만료 시간은 링크가 사용 가능하고 리소스에 접근할 수 있는 기간입니다. 이 시간이 지나면 링크는 더 이상 작동하지 않으며, 이 링크를 사용한 사용자는 리소스에 대한 접근 권한을 잃게 됩니다.",
@@ -208,11 +209,33 @@
"resourcesSearch": "리소스 검색...",
"resourceAdd": "리소스 추가",
"resourceErrorDelte": "리소스 삭제 중 오류 발생",
"resourcePoliciesTitle": "Manage Resource Policies",
"resourcePoliciesAttachedResourcesColumnTitle": "Attached resources",
"resourcePoliciesAttachedResources": "{count} resource(s)",
"resourcePoliciesAttachedResourcesEmpty": "no resources",
"resourcePoliciesDescription": "Create and manage authentication policies to control access to your resources",
"resourcePoliciesSearch": "Search policies...",
"resourcePoliciesAdd": "Add Policy",
"resourcePoliciesDefaultBadgeText": "Default policy",
"resourcePoliciesCreate": "Create Resource Policy",
"resourcePoliciesCreateDescription": "Follow the steps below to create a new policy",
"resourcePolicyName": "Policy Name",
"resourcePolicyNameDescription": "Give this policy a name to identify it across your resources",
"resourcePolicyNamePlaceholder": "e.g. Internal Access Policy",
"resourcePoliciesSeeAll": "See All Policies",
"resourcePolicyAuthMethodAdd": "Add Authentication Method",
"resourcePolicyOtpEmailAdd": "Add OTP emails",
"resourcePolicyRulesAdd": "Add Rules",
"resourcePolicyAuthMethodsDescription": "Allow access to resources via additional auth methods",
"resourcePolicyUsersRolesDescription": "Configure which users and roles can visit associated resources",
"rulesResourcePolicyDescription": "Configure rules to control access resources associated to this policy",
"authentication": "인증",
"protected": "보호됨",
"notProtected": "보호되지 않음",
"resourceMessageRemove": "제거되면 리소스에 더 이상 접근할 수 없습니다. 리소스와 연결된 모든 대상도 제거됩니다.",
"resourceQuestionRemove": "조직에서 리소스를 제거하시겠습니까?",
"resourcePolicyMessageRemove": "Once removed, the resource policy will no longer be accessible. All resources associated with the resource will be unlinked and left without authentication.",
"resourcePolicyQuestionRemove": "Are you sure you want to remove the resource policy from the organization?",
"resourceHTTP": "HTTPS 리소스",
"resourceHTTPDescription": "완전한 도메인 이름을 사용해 RAW 또는 HTTPS로 프록시 요청을 수행합니다.",
"resourceRaw": "원시 TCP/UDP 리소스",
@@ -220,8 +243,9 @@
"resourceRawDescriptionCloud": "포트 번호를 사용하여 원격 노드에 연결해야 합니다. 원격 노드에서 리소스를 사용하려면 사용자 지정 도메인을 사용하십시오.",
"resourceCreate": "리소스 생성",
"resourceCreateDescription": "아래 단계를 따라 새 리소스를 생성하세요.",
"resourceCreateGeneralDescription": "Configure the basic resource settings including the name and the type",
"resourceSeeAll": "모든 리소스 보기",
"resourceInfo": "리소스 정보",
"resourceCreateGeneral": "General",
"resourceNameDescription": "이것은 리소스의 표시 이름입니다.",
"siteSelect": "사이트 선택",
"siteSearch": "사이트 검색",
@@ -231,12 +255,15 @@
"noCountryFound": "국가를 찾을 수 없습니다.",
"siteSelectionDescription": "이 사이트는 대상에 대한 연결을 제공합니다.",
"resourceType": "리소스 유형",
"resourceTypeDescription": "리소스에 액세스하는 방법을 결정하세요.",
"resourceTypeDescription": "This controls the resource protocol and how it will be rendered in the browser. This cant be changed later.",
"resourceDomainDescription": "The resource will be served at this fully qualified domain name.",
"resourceHTTPSSettings": "HTTPS 설정",
"resourceHTTPSSettingsDescription": "리소스가 HTTPS로 접근할 수 있는 방식을 구성합니다.",
"resourcePortDescription": "The external port on the Pangolin instance or node where the resource will be accessible.",
"domainType": "도메인 유형",
"subdomain": "서브도메인",
"baseDomain": "기본 도메인",
"configure": "Configure",
"subdomnainDescription": "리소스에 접근할 수 있는 하위 도메인입니다.",
"resourceRawSettings": "TCP/UDP 설정",
"resourceRawSettingsDescription": "TCP/UDP를 통해 리소스에 접근하는 방법을 구성하세요.",
@@ -253,8 +280,27 @@
"resourceLearnRaw": "TCP/UDP 리소스 구성 방법 알아보기",
"resourceBack": "리소스로 돌아가기",
"resourceGoTo": "리소스로 이동",
"resourcePolicyDelete": "Delete Resource Policy",
"resourcePolicyDeleteConfirm": "Confirm Delete Resource Policy",
"resourceDelete": "리소스 삭제",
"resourceDeleteConfirm": "리소스 삭제 확인",
"labelDelete": "Delete Label",
"labelAdd": "Add Label",
"labelCreateSuccessMessage": "Label Created Successfully",
"labelEditSuccessMessage": "Label Modified Successfully",
"labelNameField": "Label Name",
"labelColorField": "Label Color",
"labelPlaceholder": "Ex: homelab",
"labelCreate": "Create Label",
"createLabelDialogTitle": "Create Label",
"createLabelDialogDescription": "Create a new label that can be attached to this organization",
"labelEdit": "Edit Label",
"editLabelDialogTitle": "Update Label",
"editLabelDialogDescription": "Edit a new label that can be attached to this organization",
"labelDeleteConfirm": "Confirm Delete Label",
"labelErrorDelete": "Failed to delete label",
"labelMessageRemove": "This action is permanent. All sites, resources, and clients tagged with this label will be untagged.",
"labelQuestionRemove": "Are you sure you want to remove the label from the organization?",
"visibility": "가시성",
"enabled": "활성화됨",
"disabled": "비활성화됨",
@@ -265,6 +311,8 @@
"rules": "규칙",
"resourceSettingDescription": "리소스의 설정을 구성하세요.",
"resourceSetting": "{resourceName} 설정",
"resourcePolicySettingDescription": "Configure the settings on the resource policy",
"resourcePolicySetting": "{policyName} Settings",
"alwaysAllow": "인증 우회",
"alwaysDeny": "접근 차단",
"passToAuth": "인증으로 전달",
@@ -630,7 +678,7 @@
"createdAt": "생성일",
"proxyErrorInvalidHeader": "잘못된 사용자 정의 호스트 헤더 값입니다. 도메인 이름 형식을 사용하거나 사용자 정의 호스트 헤더를 해제하려면 비워 두십시오.",
"proxyErrorTls": "유효하지 않은 TLS 서버 이름입니다. 도메인 이름 형식을 사용하거나 비워 두어 TLS 서버 이름을 제거하십시오.",
"proxyEnableSSL": "TLS 활성화",
"proxyEnableSSL": "Enable TLS",
"proxyEnableSSLDescription": "타겟과의 안전한 HTTPS 연결을 위한 SSL/TLS 암호화를 활성화하세요.",
"target": "대상",
"configureTarget": "대상 구성",
@@ -747,6 +795,16 @@
"rulesNoOne": "규칙이 없습니다. 양식을 사용하여 규칙을 추가하십시오.",
"rulesOrder": "규칙은 우선 순위에 따라 오름차순으로 평가됩니다.",
"rulesSubmit": "규칙 저장",
"policyErrorCreate": "Error creating policy",
"policyErrorCreateDescription": "An error occurred when creating the policy",
"policyErrorCreateMessageDescription": "An unexpected error occurred",
"policyErrorUpdate": "Error updating policy",
"policyErrorUpdateDescription": "An error occurred when updating the policy",
"policyErrorUpdateMessageDescription": "An unexpected error occurred",
"policyCreatedSuccess": "Resource policy succesfully created",
"policyUpdatedSuccess": "Resource policy succesfully updated",
"authMethodsSave": "Save auth methods",
"rulesSave": "Save Rules",
"resourceErrorCreate": "리소스 생성 오류",
"resourceErrorCreateDescription": "리소스를 생성하는 중 오류가 발생했습니다.",
"resourceErrorCreateMessage": "리소스 생성 오류:",
@@ -810,6 +868,16 @@
"pincodeAdd": "PIN 코드 추가",
"pincodeRemove": "PIN 코드 제거",
"resourceAuthMethods": "인증 방법",
"resourcePolicyAuthMethodsEmpty": "No authentication method",
"resourcePolicyOtpEmpty": "No one time password",
"resourcePolicyReadOnly": "This policy is Read only",
"resourcePolicyReadOnlyDescription": "This resource policy is shared accross multiple resources, you cannot edit it on this page.",
"resourcePolicyTypeSave": "Save Resource type",
"resourcePolicySelect": "Select resource policy",
"resourcePolicySelectError": "Select a resource policy",
"resourcePolicyNotFound": "Policy not found",
"resourcePolicySearch": "Search policies",
"resourcePolicyRulesEmpty": "No authentication rules",
"resourceAuthMethodsDescriptions": "추가 인증 방법을 통해 리소스에 대한 액세스 허용",
"resourceAuthSettingsSave": "성공적으로 저장되었습니다.",
"resourceAuthSettingsSaveDescription": "인증 설정이 저장되었습니다",
@@ -845,6 +913,12 @@
"resourcePincodeSetupTitle": "핀코드 설정",
"resourcePincodeSetupTitleDescription": "이 리소스를 보호하기 위해 핀 코드를 설정하십시오.",
"resourceRoleDescription": "관리자는 항상 이 리소스에 접근할 수 있습니다.",
"resourcePolicySelectTitle": "Resource Access Policy",
"resourcePolicySelectDescription": "Select the resource policy type for authentication",
"resourcePolicyInline": "Inline Resource Policy",
"resourcePolicyInlineDescription": "Access Policy scoped to only this resource",
"resourcePolicyShared": "Shared Resource Policy",
"resourcePolicySharedDescription": "Access Policy shared accross multiple resources",
"resourceUsersRoles": "접근 제어",
"resourceUsersRolesDescription": "이 리소스를 방문할 수 있는 사용자 및 역할을 구성하십시오",
"resourceUsersRolesSubmit": "접근 제어 저장",
@@ -1140,6 +1214,18 @@
"idpErrorConnectingTo": "{name}에 연결하는 데 문제가 발생했습니다. 관리자에게 문의하십시오.",
"idpErrorNotFound": "IdP를 찾을 수 없습니다.",
"inviteInvalid": "유효하지 않은 초대",
"labels": "Labels",
"orgLabelsDescription": "Manage labels in this organization.",
"addLabels": "Add labels",
"siteLabelsTab": "Labels",
"siteLabelsDescription": "Manage labels associated with this site.",
"labelsNotFound": "Labels not found",
"labelSearch": "Search labels",
"accessLabelFilterCount": "{count, plural, one {# label} other {# labels}}",
"labelOverflowCount": "+{count, plural, one {# label} other {# labels}}",
"accessLabelFilterClear": "Clear label filters",
"selectColor": "Select color",
"createNewLabel": "Create new org label \"{label}\"",
"inviteInvalidDescription": "초대 링크가 유효하지 않습니다.",
"inviteErrorWrongUser": "이 초대는 이 사용자에게 해당되지 않습니다",
"inviteErrorUserNotExists": "사용자가 존재하지 않습니다. 먼저 계정을 생성해 주세요.",
@@ -1374,6 +1460,8 @@
"sidebarResources": "리소스",
"sidebarProxyResources": "공유",
"sidebarClientResources": "비공개",
"sidebarPolicies": "Policies",
"sidebarResourcePolicies": "Resources",
"sidebarAccessControl": "액세스 제어",
"sidebarLogsAndAnalytics": "로그 및 분석",
"sidebarTeam": "팀",
@@ -1557,7 +1645,8 @@
"standaloneHcFilterSiteIdFallback": "사이트 {id}",
"standaloneHcFilterResourceIdFallback": "리소스 {id}",
"blueprints": "청사진",
"blueprintsDescription": "선언적 구성을 적용하고 이전 실행을 봅니다",
"blueprintsLog": "Blueprints Log",
"blueprintsDescription": "View past blueprint applications and their results",
"blueprintAdd": "청사진 추가",
"blueprintGoBack": "모든 청사진 보기",
"blueprintCreate": "청사진 생성",
@@ -1575,7 +1664,17 @@
"contents": "콘텐츠",
"parsedContents": "구문 분석된 콘텐츠 (읽기 전용)",
"enableDockerSocket": "Docker 청사진 활성화",
"enableDockerSocketDescription": "블루프린트 레이블을 위한 Docker 소켓 레이블 수집을 활성화합니다. 소켓 경로는 Newt에 제공되어야 합니다.",
"enableDockerSocketDescription": "Enable Docker Socket label scraping for blueprint labels. Socket path must be provided to the site connector. Read about how this works in <docsLink>the documentation</docsLink>.",
"newtAutoUpdate": "Enable Site Auto-Update",
"newtAutoUpdateDescription": "When enabled, site connectors will automatically update to the latest version when a new release is available.",
"siteAutoUpdate": "Site Auto-Update",
"siteAutoUpdateLabel": "Enable Auto-Update",
"siteAutoUpdateDescription": "Control whether this site's connector automatically downloads the latest version.",
"siteAutoUpdateOrgDefault": "Organization default: {state}",
"siteAutoUpdateOverriding": "Overriding organization setting",
"siteAutoUpdateResetToOrg": "Reset to Organization Default",
"siteAutoUpdateEnabled": "enabled",
"siteAutoUpdateDisabled": "disabled",
"viewDockerContainers": "도커 컨테이너 보기",
"containersIn": "{siteName}의 컨테이너",
"selectContainerDescription": "이 대상을 위한 호스트 이름으로 사용할 컨테이너를 선택하세요. 포트를 사용하려면 포트를 클릭하세요.",
@@ -1620,6 +1719,7 @@
"certificateStatus": "인증서",
"certificateStatusAutoRefreshHint": "상태가 자동으로 새로 고쳐집니다.",
"loading": "로딩 중",
"loadingEllipsis": "Loading...",
"loadingAnalytics": "분석 로딩 중",
"restart": "재시작",
"domains": "도메인",
@@ -1846,6 +1946,7 @@
"billingManageLicenseSubscription": "유료 독립 호스트 라이센스 키를 위한 구독 관리",
"billingCurrentKeys": "현재 키",
"billingModifyCurrentPlan": "현재 계획 수정",
"billingManageLicenseSubscriptionDescription": "Manage your subscription for paid self-hosted license keys and download invoices.",
"billingConfirmUpgrade": "업그레이드 확인",
"billingConfirmDowngrade": "다운그레이드 확인",
"billingConfirmUpgradeDescription": "계획을 업그레이드하려고 합니다. 아래의 새로운 제한 및 가격을 검토하세요.",
@@ -1943,7 +2044,36 @@
"timeIsInSeconds": "시간은 초 단위입니다",
"requireDeviceApproval": "장치 승인 요구",
"requireDeviceApprovalDescription": "이 역할을 가진 사용자는 장치가 연결되기 전에 관리자의 승인이 필요합니다.",
"sshAccess": "SSH 접속",
"sshSettings": "SSH Settings",
"rdpSettings": "RDP Settings",
"vncSettings": "VNC Settings",
"sshServer": "SSH Server",
"rdpServer": "RDP Server",
"vncServer": "VNC Server",
"sshServerDescription": "Set up the authentication method, daemon location, and server destination",
"rdpServerDescription": "Configure the destination and port of the RDP server",
"vncServerDescription": "Configure the destination and port of the VNC server",
"sshServerMode": "Mode",
"sshServerModeStandard": "Standard SSH Server",
"sshServerModePangolin": "Pangolin SSH",
"sshServerModeStandardDescription": "Routes commands over network to an SSH server such as OpenSSH.",
"sshServerModeNative": "Native SSH Server",
"sshServerModeNativeDescription": "Executes commands directly on the host via the Site Connector. No network config required.",
"sshAuthenticationMethod": "Authentication Method",
"sshAuthMethodManual": "Manual Authentication",
"sshAuthMethodManualDescription": "Requires existing host credentials. Bypasses automatic provisioning.",
"sshAuthMethodAutomated": "Automated Provisioning",
"sshAuthMethodAutomatedDescription": "Automatically creates users, groups, and sudo permissions on host.",
"sshAuthDaemonLocation": "Auth Daemon Location",
"sshDaemonLocationSiteDescription": "Executes locally on the machine hosting the site connector.",
"sshDaemonLocationRemote": "On Remote Host",
"sshDaemonLocationRemoteDescription": "Executes on a separate target machine on the same network.",
"sshDaemonDisclaimer": "Ensure your target host is properly configured to run the auth daemon before completing this setup, or provisioning will fail.",
"sshDaemonPort": "Daemon Port",
"sshServerDestination": "Server Destination",
"sshServerDestinationDescription": "Configure the destination and port of the SSH server",
"destination": "Destination",
"bgTargetMultiSiteDisclaimer": "Selecting multiple sites enables resilient routing and failover for high availability.",
"roleAllowSsh": "SSH 허용",
"roleAllowSshAllow": "허용",
"roleAllowSshDisallow": "허용 안 함",
@@ -1957,7 +2087,7 @@
"sshSudoModeCommandsDescription": "사용자는 sudo로 지정된 명령만 실행할 수 있습니다.",
"sshSudo": "Sudo 허용",
"sshSudoCommands": "Sudo 명령",
"sshSudoCommandsDescription": "사용자가 sudo로 실행할 수 있는 명령어의 쉼표로 구분된 목록입니다.",
"sshSudoCommandsDescription": "Comma separated list of commands the user is allowed to run with sudo. Absolute paths must be used.",
"sshCreateHomeDir": "홈 디렉터리 생성",
"sshUnixGroups": "유닉스 그룹",
"sshUnixGroupsDescription": "대상 호스트에서 사용자에게 추가할 유닉스 그룹의 쉼표로 구분된 목록입니다.",
@@ -2049,8 +2179,9 @@
"editInternalResourceDialogModeCidr": "CIDR",
"editInternalResourceDialogModeHttp": "HTTP",
"editInternalResourceDialogModeHttps": "HTTPS",
"editInternalResourceDialogModeSsh": "SSH",
"editInternalResourceDialogScheme": "스킴",
"editInternalResourceDialogEnableSsl": "TLS 활성화",
"editInternalResourceDialogEnableSsl": "Enable TLS",
"editInternalResourceDialogEnableSslDescription": "목적지로의 안전한 HTTPS 연결을 위한 SSL/TLS 암호화 활성화.",
"editInternalResourceDialogDestination": "대상지",
"editInternalResourceDialogDestinationHostDescription": "사이트 네트워크의 자원 IP 주소입니다.",
@@ -2098,9 +2229,10 @@
"createInternalResourceDialogModeCidr": "CIDR",
"createInternalResourceDialogModeHttp": "HTTP",
"createInternalResourceDialogModeHttps": "HTTPS",
"createInternalResourceDialogModeSsh": "SSH",
"scheme": "스킴",
"createInternalResourceDialogScheme": "스킴",
"createInternalResourceDialogEnableSsl": "TLS 활성화",
"createInternalResourceDialogEnableSsl": "Enable TLS",
"createInternalResourceDialogEnableSslDescription": "목적지로의 안전한 HTTPS 연결을 위한 SSL/TLS 암호화 활성화.",
"createInternalResourceDialogDestination": "대상지",
"createInternalResourceDialogDestinationHostDescription": "사이트 네트워크의 자원 IP 주소입니다.",
@@ -2233,7 +2365,7 @@
"description": "더 신뢰할 수 있고 낮은 유지보수의 자체 호스팅 팡골린 서버, 추가 기능 포함",
"introTitle": "관리 자체 호스팅 팡골린",
"introDescription": "는 자신의 데이터를 프라이빗하고 자체 호스팅을 유지하면서 더 간단하고 추가적인 신뢰성을 원하는 사람들을 위한 배포 옵션입니다.",
"introDetail": "이 옵션을 사용하면 여전히 자신의 팡골린 노드를 운영하고 - 터널, TLS 종료 및 트래픽 모두 서버에 유지됩니다. 차이점은 관리 및 모니터링이 클라우드 대시보드를 통해 처리되어 여러 혜택을 제공합니다.",
"introDetail": "With this option, you still run your own Pangolin node - your tunnels, TLS termination, and traffic all stay on your server. The difference is that management and monitoring are handled through our cloud dashboard, which unlocks a number of benefits:",
"benefitSimplerOperations": {
"title": "더 간단한 운영",
"description": "자체 메일 서버를 운영하거나 복잡한 경고를 설정할 필요가 없습니다. 기본적으로 상태 점검 및 다운타임 경고를 받을 수 있습니다."
@@ -2937,7 +3069,7 @@
"learnMore": "자세히 알아보기",
"backToHome": "홈으로 돌아가기",
"needToSignInToOrg": "조직의 아이덴티티 공급자를 사용해야 합니까?",
"maintenanceMode": "유지보수 모드",
"maintenanceMode": "Maintenance Page",
"maintenanceModeDescription": "방문자에게 유지보수 페이지 표시",
"maintenanceModeType": "유지보수 모드 유형",
"showMaintenancePage": "방문자에게 유지보수 페이지 표시",
@@ -2967,6 +3099,7 @@
"maintenanceScreenEstimatedCompletion": "예상 완료:",
"createInternalResourceDialogDestinationRequired": "목적지가 필요합니다.",
"available": "사용 가능",
"disabledResourceDescription": "When disabled, the resource will be inaccessible by everyone.",
"archived": "보관된",
"noArchivedDevices": "보관된 장치가 없습니다.",
"deviceArchived": "장치가 보관되었습니다.",
@@ -3296,5 +3429,6 @@
"memberPortalResourceDisabled": "리소스 비활성화됨",
"memberPortalShowingResources": "{start}-{end} 중 {total}개의 리소스를 표시 중",
"memberPortalPrevious": "이전",
"memberPortalNext": "다음"
"memberPortalNext": "다음",
"httpSettings": "HTTP Settings"
}

View File

@@ -176,6 +176,7 @@
"shareErrorCreateDescription": "Det oppsto en feil ved opprettelse av delingslenken",
"shareCreateDescription": "Alle med denne lenken får tilgang til ressursen",
"shareTitleOptional": "Tittel (valgfritt)",
"sharePathOptional": "Path (optional)",
"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.",
@@ -208,11 +209,33 @@
"resourcesSearch": "Søk i ressurser...",
"resourceAdd": "Legg til ressurs",
"resourceErrorDelte": "Feil ved sletting av ressurs",
"resourcePoliciesTitle": "Manage Resource Policies",
"resourcePoliciesAttachedResourcesColumnTitle": "Attached resources",
"resourcePoliciesAttachedResources": "{count} resource(s)",
"resourcePoliciesAttachedResourcesEmpty": "no resources",
"resourcePoliciesDescription": "Create and manage authentication policies to control access to your resources",
"resourcePoliciesSearch": "Search policies...",
"resourcePoliciesAdd": "Add Policy",
"resourcePoliciesDefaultBadgeText": "Default policy",
"resourcePoliciesCreate": "Create Resource Policy",
"resourcePoliciesCreateDescription": "Follow the steps below to create a new policy",
"resourcePolicyName": "Policy Name",
"resourcePolicyNameDescription": "Give this policy a name to identify it across your resources",
"resourcePolicyNamePlaceholder": "e.g. Internal Access Policy",
"resourcePoliciesSeeAll": "See All Policies",
"resourcePolicyAuthMethodAdd": "Add Authentication Method",
"resourcePolicyOtpEmailAdd": "Add OTP emails",
"resourcePolicyRulesAdd": "Add Rules",
"resourcePolicyAuthMethodsDescription": "Allow access to resources via additional auth methods",
"resourcePolicyUsersRolesDescription": "Configure which users and roles can visit associated resources",
"rulesResourcePolicyDescription": "Configure rules to control access resources associated to this policy",
"authentication": "Autentisering",
"protected": "Beskyttet",
"notProtected": "Ikke beskyttet",
"resourceMessageRemove": "Når den er fjernet, vil ressursen ikke lenger være tilgjengelig. Alle mål knyttet til ressursen vil også bli fjernet.",
"resourceQuestionRemove": "Er du sikker på at du vil fjerne ressursen fra organisasjonen?",
"resourcePolicyMessageRemove": "Once removed, the resource policy will no longer be accessible. All resources associated with the resource will be unlinked and left without authentication.",
"resourcePolicyQuestionRemove": "Are you sure you want to remove the resource policy from the organization?",
"resourceHTTP": "HTTPS-ressurs",
"resourceHTTPDescription": "Proxy forespørsler over HTTPS ved å bruke et fullstendig kvalifisert domenenavn.",
"resourceRaw": "Rå TCP/UDP-ressurs",
@@ -220,8 +243,9 @@
"resourceRawDescriptionCloud": "Proxy forespørsler om rå TCP/UDP ved hjelp av et portnummer. Krever sider for å koble til en ekstern node.",
"resourceCreate": "Opprett ressurs",
"resourceCreateDescription": "Følg trinnene nedenfor for å opprette en ny ressurs",
"resourceCreateGeneralDescription": "Configure the basic resource settings including the name and the type",
"resourceSeeAll": "Se alle ressurser",
"resourceInfo": "Ressursinformasjon",
"resourceCreateGeneral": "General",
"resourceNameDescription": "Dette er visningsnavnet for ressursen.",
"siteSelect": "Velg område",
"siteSearch": "Søk i område",
@@ -231,12 +255,15 @@
"noCountryFound": "Ingen land funnet.",
"siteSelectionDescription": "Dette området vil gi tilkobling til mål.",
"resourceType": "Ressurstype",
"resourceTypeDescription": "Bestemme hvordan denne ressursen skal brukes",
"resourceTypeDescription": "This controls the resource protocol and how it will be rendered in the browser. This cant be changed later.",
"resourceDomainDescription": "The resource will be served at this fully qualified domain name.",
"resourceHTTPSSettings": "HTTPS-innstillinger",
"resourceHTTPSSettingsDescription": "Konfigurer hvordan ressursen skal nås over HTTPS",
"resourcePortDescription": "The external port on the Pangolin instance or node where the resource will be accessible.",
"domainType": "Domenetype",
"subdomain": "Underdomene",
"baseDomain": "Grunndomene",
"configure": "Configure",
"subdomnainDescription": "Underdomenet hvor ressursen vil være tilgjengelig.",
"resourceRawSettings": "TCP/UDP-innstillinger",
"resourceRawSettingsDescription": "Konfigurer hvordan ressursen vil bli tilgjengelig over TCP/UDP",
@@ -253,8 +280,27 @@
"resourceLearnRaw": "Lær hvordan å konfigurere TCP/UDP-ressurser",
"resourceBack": "Tilbake til ressurser",
"resourceGoTo": "Gå til ressurs",
"resourcePolicyDelete": "Delete Resource Policy",
"resourcePolicyDeleteConfirm": "Confirm Delete Resource Policy",
"resourceDelete": "Slett ressurs",
"resourceDeleteConfirm": "Bekreft sletting av ressurs",
"labelDelete": "Delete Label",
"labelAdd": "Add Label",
"labelCreateSuccessMessage": "Label Created Successfully",
"labelEditSuccessMessage": "Label Modified Successfully",
"labelNameField": "Label Name",
"labelColorField": "Label Color",
"labelPlaceholder": "Ex: homelab",
"labelCreate": "Create Label",
"createLabelDialogTitle": "Create Label",
"createLabelDialogDescription": "Create a new label that can be attached to this organization",
"labelEdit": "Edit Label",
"editLabelDialogTitle": "Update Label",
"editLabelDialogDescription": "Edit a new label that can be attached to this organization",
"labelDeleteConfirm": "Confirm Delete Label",
"labelErrorDelete": "Failed to delete label",
"labelMessageRemove": "This action is permanent. All sites, resources, and clients tagged with this label will be untagged.",
"labelQuestionRemove": "Are you sure you want to remove the label from the organization?",
"visibility": "Synlighet",
"enabled": "Aktivert",
"disabled": "Deaktivert",
@@ -265,6 +311,8 @@
"rules": "Regler",
"resourceSettingDescription": "Konfigurere innstillingene på ressursen",
"resourceSetting": "{resourceName} Innstillinger",
"resourcePolicySettingDescription": "Configure the settings on the resource policy",
"resourcePolicySetting": "{policyName} Settings",
"alwaysAllow": "Omgå Auth",
"alwaysDeny": "Blokker tilgang",
"passToAuth": "Pass til Autentisering",
@@ -630,7 +678,7 @@
"createdAt": "Opprettet",
"proxyErrorInvalidHeader": "Ugyldig verdi for egendefinert vertsoverskrift. Bruk domenenavnformat, eller lagre tomt for å fjerne den egendefinerte vertsoverskriften.",
"proxyErrorTls": "Ugyldig TLS-servernavn. Bruk domenenavnformat, eller la stå tomt for å fjerne TLS-servernavnet.",
"proxyEnableSSL": "Aktiver TLS",
"proxyEnableSSL": "Enable TLS",
"proxyEnableSSLDescription": "Aktivere SSL/TLS-kryptering for sikker HTTPS tilkobling til målene.",
"target": "Target",
"configureTarget": "Konfigurer mål",
@@ -747,6 +795,16 @@
"rulesNoOne": "Ingen regler. Legg til en regel ved å bruke skjemaet.",
"rulesOrder": "Regler evalueres etter prioritet i stigende rekkefølge.",
"rulesSubmit": "Lagre regler",
"policyErrorCreate": "Error creating policy",
"policyErrorCreateDescription": "An error occurred when creating the policy",
"policyErrorCreateMessageDescription": "An unexpected error occurred",
"policyErrorUpdate": "Error updating policy",
"policyErrorUpdateDescription": "An error occurred when updating the policy",
"policyErrorUpdateMessageDescription": "An unexpected error occurred",
"policyCreatedSuccess": "Resource policy succesfully created",
"policyUpdatedSuccess": "Resource policy succesfully updated",
"authMethodsSave": "Save auth methods",
"rulesSave": "Save Rules",
"resourceErrorCreate": "Feil under oppretting av ressurs",
"resourceErrorCreateDescription": "Det oppstod en feil under oppretting av ressursen",
"resourceErrorCreateMessage": "Feil ved oppretting av ressurs:",
@@ -810,6 +868,16 @@
"pincodeAdd": "Legg til PIN-kode",
"pincodeRemove": "Fjern PIN-kode",
"resourceAuthMethods": "Autentiseringsmetoder",
"resourcePolicyAuthMethodsEmpty": "No authentication method",
"resourcePolicyOtpEmpty": "No one time password",
"resourcePolicyReadOnly": "This policy is Read only",
"resourcePolicyReadOnlyDescription": "This resource policy is shared accross multiple resources, you cannot edit it on this page.",
"resourcePolicyTypeSave": "Save Resource type",
"resourcePolicySelect": "Select resource policy",
"resourcePolicySelectError": "Select a resource policy",
"resourcePolicyNotFound": "Policy not found",
"resourcePolicySearch": "Search policies",
"resourcePolicyRulesEmpty": "No authentication rules",
"resourceAuthMethodsDescriptions": "Tillat tilgang til ressursen via ytterligere autentiseringsmetoder",
"resourceAuthSettingsSave": "Lagret vellykket",
"resourceAuthSettingsSaveDescription": "Autentiseringsinnstillinger er lagret",
@@ -845,6 +913,12 @@
"resourcePincodeSetupTitle": "Angi PIN-kode",
"resourcePincodeSetupTitleDescription": "Sett en pinkode for å beskytte denne ressursen",
"resourceRoleDescription": "Administratorer har alltid tilgang til denne ressursen.",
"resourcePolicySelectTitle": "Resource Access Policy",
"resourcePolicySelectDescription": "Select the resource policy type for authentication",
"resourcePolicyInline": "Inline Resource Policy",
"resourcePolicyInlineDescription": "Access Policy scoped to only this resource",
"resourcePolicyShared": "Shared Resource Policy",
"resourcePolicySharedDescription": "Access Policy shared accross multiple resources",
"resourceUsersRoles": "Tilgangskontroller",
"resourceUsersRolesDescription": "Konfigurer hvilke brukere og roller som har tilgang til denne ressursen",
"resourceUsersRolesSubmit": "Lagre tilgangskontroller",
@@ -1140,6 +1214,18 @@
"idpErrorConnectingTo": "Det oppstod et problem med å koble til {name}. Vennligst kontakt din administrator.",
"idpErrorNotFound": "IdP ikke funnet",
"inviteInvalid": "Ugyldig invitasjon",
"labels": "Labels",
"orgLabelsDescription": "Manage labels in this organization.",
"addLabels": "Add labels",
"siteLabelsTab": "Labels",
"siteLabelsDescription": "Manage labels associated with this site.",
"labelsNotFound": "Labels not found",
"labelSearch": "Search labels",
"accessLabelFilterCount": "{count, plural, one {# label} other {# labels}}",
"labelOverflowCount": "+{count, plural, one {# label} other {# labels}}",
"accessLabelFilterClear": "Clear label filters",
"selectColor": "Select color",
"createNewLabel": "Create new org label \"{label}\"",
"inviteInvalidDescription": "Invitasjonslenken er ugyldig.",
"inviteErrorWrongUser": "Invitasjonen er ikke for denne brukeren",
"inviteErrorUserNotExists": "Brukeren eksisterer ikke. Vennligst opprett en konto først.",
@@ -1374,6 +1460,8 @@
"sidebarResources": "Ressurser",
"sidebarProxyResources": "Offentlig",
"sidebarClientResources": "Privat",
"sidebarPolicies": "Policies",
"sidebarResourcePolicies": "Resources",
"sidebarAccessControl": "Tilgangskontroll",
"sidebarLogsAndAnalytics": "Logger og analyser",
"sidebarTeam": "Lag",
@@ -1557,7 +1645,8 @@
"standaloneHcFilterSiteIdFallback": "Område {id}",
"standaloneHcFilterResourceIdFallback": "Ressurs {id}",
"blueprints": "Tegninger",
"blueprintsDescription": "Bruk deklarative konfigurasjoner og vis tidligere kjøringer",
"blueprintsLog": "Blueprints Log",
"blueprintsDescription": "View past blueprint applications and their results",
"blueprintAdd": "Legg til blåkopi",
"blueprintGoBack": "Se alle blåkopier",
"blueprintCreate": "Opprette mal",
@@ -1575,7 +1664,17 @@
"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.",
"enableDockerSocketDescription": "Enable Docker Socket label scraping for blueprint labels. Socket path must be provided to the site connector. Read about how this works in <docsLink>the documentation</docsLink>.",
"newtAutoUpdate": "Enable Site Auto-Update",
"newtAutoUpdateDescription": "When enabled, site connectors will automatically update to the latest version when a new release is available.",
"siteAutoUpdate": "Site Auto-Update",
"siteAutoUpdateLabel": "Enable Auto-Update",
"siteAutoUpdateDescription": "Control whether this site's connector automatically downloads the latest version.",
"siteAutoUpdateOrgDefault": "Organization default: {state}",
"siteAutoUpdateOverriding": "Overriding organization setting",
"siteAutoUpdateResetToOrg": "Reset to Organization Default",
"siteAutoUpdateEnabled": "enabled",
"siteAutoUpdateDisabled": "disabled",
"viewDockerContainers": "Vis Docker-containere",
"containersIn": "Containere i {siteName}",
"selectContainerDescription": "Velg en hvilken som helst container for å bruke som vertsnavn for dette målet. Klikk på en port for å bruke en port.",
@@ -1620,6 +1719,7 @@
"certificateStatus": "Sertifikat",
"certificateStatusAutoRefreshHint": "Status oppdateres automatisk.",
"loading": "Laster inn",
"loadingEllipsis": "Loading...",
"loadingAnalytics": "Laster inn analyser",
"restart": "Start på nytt",
"domains": "Domener",
@@ -1846,6 +1946,7 @@
"billingManageLicenseSubscription": "Administrer abonnementet for betalte lisensnøkler selv hostet",
"billingCurrentKeys": "Nåværende nøkler",
"billingModifyCurrentPlan": "Endre gjeldende plan",
"billingManageLicenseSubscriptionDescription": "Manage your subscription for paid self-hosted license keys and download invoices.",
"billingConfirmUpgrade": "Bekreft oppgradering",
"billingConfirmDowngrade": "Bekreft nedgradering",
"billingConfirmUpgradeDescription": "Du er i ferd med å oppgradere abonnementet ditt. Gå gjennom de nye grensene og pris nedenfor.",
@@ -1943,7 +2044,36 @@
"timeIsInSeconds": "Tid er i sekunder",
"requireDeviceApproval": "Krev enhetsgodkjenning",
"requireDeviceApprovalDescription": "Brukere med denne rollen trenger nye enheter godkjent av en admin før de kan koble seg og få tilgang til ressurser.",
"sshAccess": "SSH tilgang",
"sshSettings": "SSH Settings",
"rdpSettings": "RDP Settings",
"vncSettings": "VNC Settings",
"sshServer": "SSH Server",
"rdpServer": "RDP Server",
"vncServer": "VNC Server",
"sshServerDescription": "Set up the authentication method, daemon location, and server destination",
"rdpServerDescription": "Configure the destination and port of the RDP server",
"vncServerDescription": "Configure the destination and port of the VNC server",
"sshServerMode": "Mode",
"sshServerModeStandard": "Standard SSH Server",
"sshServerModePangolin": "Pangolin SSH",
"sshServerModeStandardDescription": "Routes commands over network to an SSH server such as OpenSSH.",
"sshServerModeNative": "Native SSH Server",
"sshServerModeNativeDescription": "Executes commands directly on the host via the Site Connector. No network config required.",
"sshAuthenticationMethod": "Authentication Method",
"sshAuthMethodManual": "Manual Authentication",
"sshAuthMethodManualDescription": "Requires existing host credentials. Bypasses automatic provisioning.",
"sshAuthMethodAutomated": "Automated Provisioning",
"sshAuthMethodAutomatedDescription": "Automatically creates users, groups, and sudo permissions on host.",
"sshAuthDaemonLocation": "Auth Daemon Location",
"sshDaemonLocationSiteDescription": "Executes locally on the machine hosting the site connector.",
"sshDaemonLocationRemote": "On Remote Host",
"sshDaemonLocationRemoteDescription": "Executes on a separate target machine on the same network.",
"sshDaemonDisclaimer": "Ensure your target host is properly configured to run the auth daemon before completing this setup, or provisioning will fail.",
"sshDaemonPort": "Daemon Port",
"sshServerDestination": "Server Destination",
"sshServerDestinationDescription": "Configure the destination and port of the SSH server",
"destination": "Destination",
"bgTargetMultiSiteDisclaimer": "Selecting multiple sites enables resilient routing and failover for high availability.",
"roleAllowSsh": "Tillat SSH",
"roleAllowSshAllow": "Tillat",
"roleAllowSshDisallow": "Forby",
@@ -1957,7 +2087,7 @@
"sshSudoModeCommandsDescription": "Brukeren kan bare kjøre de angitte kommandoene med sudo.",
"sshSudo": "Tillat sudo",
"sshSudoCommands": "Sudo kommandoer",
"sshSudoCommandsDescription": "Kommaseparert liste med kommandoer brukeren kan kjøre med sudo.",
"sshSudoCommandsDescription": "Comma separated list of commands the user is allowed to run with sudo. Absolute paths must be used.",
"sshCreateHomeDir": "Opprett hjemmappe",
"sshUnixGroups": "Unix grupper",
"sshUnixGroupsDescription": "Kommaseparerte Unix grupper for å legge brukeren til på mål-verten.",
@@ -2049,8 +2179,9 @@
"editInternalResourceDialogModeCidr": "CIDR",
"editInternalResourceDialogModeHttp": "HTTP",
"editInternalResourceDialogModeHttps": "HTTPS",
"editInternalResourceDialogModeSsh": "SSH",
"editInternalResourceDialogScheme": "Skjema",
"editInternalResourceDialogEnableSsl": "Aktiver TLS",
"editInternalResourceDialogEnableSsl": "Enable TLS",
"editInternalResourceDialogEnableSslDescription": "Aktiver SSL/TLS-kryptering for sikre HTTPS-tilkoblinger til destinasjonen.",
"editInternalResourceDialogDestination": "Destinasjon",
"editInternalResourceDialogDestinationHostDescription": "IP-adressen eller vertsnavnet til ressursen på nettstedets nettverk.",
@@ -2098,9 +2229,10 @@
"createInternalResourceDialogModeCidr": "CIDR",
"createInternalResourceDialogModeHttp": "HTTP",
"createInternalResourceDialogModeHttps": "HTTPS",
"createInternalResourceDialogModeSsh": "SSH",
"scheme": "Skjema",
"createInternalResourceDialogScheme": "Skjema",
"createInternalResourceDialogEnableSsl": "Aktiver TLS",
"createInternalResourceDialogEnableSsl": "Enable TLS",
"createInternalResourceDialogEnableSslDescription": "Aktiver SSL/TLS-kryptering for sikre HTTPS-tilkoblinger til destinasjonen.",
"createInternalResourceDialogDestination": "Destinasjon",
"createInternalResourceDialogDestinationHostDescription": "IP-adressen eller vertsnavnet til ressursen på nettstedets nettverk.",
@@ -2233,7 +2365,7 @@
"description": "Sikre og lavvedlikeholdsservere, selvbetjente Pangolin med ekstra klokker, og understell",
"introTitle": "Administrert Self-Hosted Pangolin",
"introDescription": "er et alternativ for bruk utviklet for personer som ønsker enkel og ekstra pålitelighet mens de fortsatt holder sine data privat og selvdrevne.",
"introDetail": "Med dette valget kjører du fortsatt din egen Pangolin-node - tunneler, TLS-terminering og trafikken ligger på serveren din. Forskjellen er at behandling og overvåking håndteres gjennom vårt skydashbord, som låser opp en rekke fordeler:",
"introDetail": "With this option, you still run your own Pangolin node - your tunnels, TLS termination, and traffic all stay on your server. The difference is that management and monitoring are handled through our cloud dashboard, which unlocks a number of benefits:",
"benefitSimplerOperations": {
"title": "Enklere operasjoner",
"description": "Ingen grunn til å kjøre din egen e-postserver eller sette opp kompleks varsling. Du vil få helsesjekk og nedetid varsler ut av boksen."
@@ -2937,7 +3069,7 @@
"learnMore": "Lær mer",
"backToHome": "Gå tilbake til start",
"needToSignInToOrg": "Trenger du å bruke organisasjonens identitetsleverandør?",
"maintenanceMode": "Vedlikeholdsmodus",
"maintenanceMode": "Maintenance Page",
"maintenanceModeDescription": "Vis en vedlikeholdsside til besøkende",
"maintenanceModeType": "Vedlikeholdsmodus type",
"showMaintenancePage": "Vis en vedlikeholdsside til besøkende",
@@ -2967,6 +3099,7 @@
"maintenanceScreenEstimatedCompletion": "Estimert ferdigstillelse:",
"createInternalResourceDialogDestinationRequired": "Destinasjonen er nødvendig",
"available": "Tilgjengelig",
"disabledResourceDescription": "When disabled, the resource will be inaccessible by everyone.",
"archived": "Arkivert",
"noArchivedDevices": "Ingen arkiverte enheter funnet",
"deviceArchived": "Enhet arkivert",
@@ -3296,5 +3429,6 @@
"memberPortalResourceDisabled": "Ressurs deaktivert",
"memberPortalShowingResources": "Viser {start}-{end} av {total} ressurser",
"memberPortalPrevious": "Forrige",
"memberPortalNext": "Neste"
"memberPortalNext": "Neste",
"httpSettings": "HTTP Settings"
}

View File

@@ -176,6 +176,7 @@
"shareErrorCreateDescription": "Fout opgetreden tijdens het maken van de share link",
"shareCreateDescription": "Iedereen met deze link heeft toegang tot de pagina",
"shareTitleOptional": "Titel (optioneel)",
"sharePathOptional": "Path (optional)",
"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.",
@@ -208,11 +209,33 @@
"resourcesSearch": "Zoek bronnen...",
"resourceAdd": "Bron toevoegen",
"resourceErrorDelte": "Fout bij verwijderen document",
"resourcePoliciesTitle": "Manage Resource Policies",
"resourcePoliciesAttachedResourcesColumnTitle": "Attached resources",
"resourcePoliciesAttachedResources": "{count} resource(s)",
"resourcePoliciesAttachedResourcesEmpty": "no resources",
"resourcePoliciesDescription": "Create and manage authentication policies to control access to your resources",
"resourcePoliciesSearch": "Search policies...",
"resourcePoliciesAdd": "Add Policy",
"resourcePoliciesDefaultBadgeText": "Default policy",
"resourcePoliciesCreate": "Create Resource Policy",
"resourcePoliciesCreateDescription": "Follow the steps below to create a new policy",
"resourcePolicyName": "Policy Name",
"resourcePolicyNameDescription": "Give this policy a name to identify it across your resources",
"resourcePolicyNamePlaceholder": "e.g. Internal Access Policy",
"resourcePoliciesSeeAll": "See All Policies",
"resourcePolicyAuthMethodAdd": "Add Authentication Method",
"resourcePolicyOtpEmailAdd": "Add OTP emails",
"resourcePolicyRulesAdd": "Add Rules",
"resourcePolicyAuthMethodsDescription": "Allow access to resources via additional auth methods",
"resourcePolicyUsersRolesDescription": "Configure which users and roles can visit associated resources",
"rulesResourcePolicyDescription": "Configure rules to control access resources associated to this policy",
"authentication": "Authenticatie",
"protected": "Beschermd",
"notProtected": "Niet beveiligd",
"resourceMessageRemove": "Eenmaal verwijderd, zal het bestand niet langer toegankelijk zijn. Alle doelen die gekoppeld zijn aan het hulpbron, zullen ook verwijderd worden.",
"resourceQuestionRemove": "Weet u zeker dat u het document van de organisatie wilt verwijderen?",
"resourcePolicyMessageRemove": "Once removed, the resource policy will no longer be accessible. All resources associated with the resource will be unlinked and left without authentication.",
"resourcePolicyQuestionRemove": "Are you sure you want to remove the resource policy from the organization?",
"resourceHTTP": "HTTPS bron",
"resourceHTTPDescription": "Proxyverzoeken via HTTPS met een volledig gekwalificeerde domeinnaam.",
"resourceRaw": "TCP/UDP bron",
@@ -220,8 +243,9 @@
"resourceRawDescriptionCloud": "Proxy verzoeken over rauwe TCP/UDP met behulp van een poortnummer. Vereist sites om verbinding te maken met een remote node.",
"resourceCreate": "Bron maken",
"resourceCreateDescription": "Volg de onderstaande stappen om een nieuwe bron te maken",
"resourceCreateGeneralDescription": "Configure the basic resource settings including the name and the type",
"resourceSeeAll": "Alle bronnen bekijken",
"resourceInfo": "Bron informatie",
"resourceCreateGeneral": "General",
"resourceNameDescription": "Dit is de weergavenaam voor het document.",
"siteSelect": "Selecteer site",
"siteSearch": "Zoek site",
@@ -231,12 +255,15 @@
"noCountryFound": "Geen land gevonden.",
"siteSelectionDescription": "Deze site zal connectiviteit met het doelwit bieden.",
"resourceType": "Type bron",
"resourceTypeDescription": "Bepaal hoe u toegang wilt tot de bron",
"resourceTypeDescription": "This controls the resource protocol and how it will be rendered in the browser. This cant be changed later.",
"resourceDomainDescription": "The resource will be served at this fully qualified domain name.",
"resourceHTTPSSettings": "HTTPS instellingen",
"resourceHTTPSSettingsDescription": "Stel in hoe de bron wordt benaderd via HTTPS",
"resourcePortDescription": "The external port on the Pangolin instance or node where the resource will be accessible.",
"domainType": "Domein type",
"subdomain": "Subdomein",
"baseDomain": "Basis domein",
"configure": "Configure",
"subdomnainDescription": "Het subdomein waar de bron toegankelijk zal zijn.",
"resourceRawSettings": "TCP/UDP instellingen",
"resourceRawSettingsDescription": "Stel in hoe de bron wordt benaderd via TCP/UDP",
@@ -253,8 +280,27 @@
"resourceLearnRaw": "Leer hoe je TCP/UDP bronnen kunt configureren",
"resourceBack": "Terug naar bronnen",
"resourceGoTo": "Ga naar Resource",
"resourcePolicyDelete": "Delete Resource Policy",
"resourcePolicyDeleteConfirm": "Confirm Delete Resource Policy",
"resourceDelete": "Document verwijderen",
"resourceDeleteConfirm": "Bevestig Verwijderen Document",
"labelDelete": "Delete Label",
"labelAdd": "Add Label",
"labelCreateSuccessMessage": "Label Created Successfully",
"labelEditSuccessMessage": "Label Modified Successfully",
"labelNameField": "Label Name",
"labelColorField": "Label Color",
"labelPlaceholder": "Ex: homelab",
"labelCreate": "Create Label",
"createLabelDialogTitle": "Create Label",
"createLabelDialogDescription": "Create a new label that can be attached to this organization",
"labelEdit": "Edit Label",
"editLabelDialogTitle": "Update Label",
"editLabelDialogDescription": "Edit a new label that can be attached to this organization",
"labelDeleteConfirm": "Confirm Delete Label",
"labelErrorDelete": "Failed to delete label",
"labelMessageRemove": "This action is permanent. All sites, resources, and clients tagged with this label will be untagged.",
"labelQuestionRemove": "Are you sure you want to remove the label from the organization?",
"visibility": "Zichtbaarheid",
"enabled": "Ingeschakeld",
"disabled": "Uitgeschakeld",
@@ -265,6 +311,8 @@
"rules": "Regels",
"resourceSettingDescription": "Configureer de instellingen in de bron",
"resourceSetting": "{resourceName} instellingen",
"resourcePolicySettingDescription": "Configure the settings on the resource policy",
"resourcePolicySetting": "{policyName} Settings",
"alwaysAllow": "Authenticatie omzeilen",
"alwaysDeny": "Blokkeer toegang",
"passToAuth": "Passeren naar Auth",
@@ -630,7 +678,7 @@
"createdAt": "Aangemaakt op",
"proxyErrorInvalidHeader": "Ongeldige aangepaste Header waarde. Gebruik het domeinnaam formaat, of sla leeg op om de aangepaste Host header ongedaan te maken.",
"proxyErrorTls": "Ongeldige TLS servernaam. Gebruik de domeinnaam of sla leeg op om de TLS servernaam te verwijderen.",
"proxyEnableSSL": "TLS inschakelen",
"proxyEnableSSL": "Enable TLS",
"proxyEnableSSLDescription": "SSL/TLS-versleuteling inschakelen voor beveiligde HTTPS-verbindingen naar de doelen.",
"target": "Target",
"configureTarget": "Doelstellingen configureren",
@@ -747,6 +795,16 @@
"rulesNoOne": "Geen regels. Voeg een regel toe via het formulier.",
"rulesOrder": "Regels worden in oplopende volgorde volgens prioriteit beoordeeld.",
"rulesSubmit": "Regels opslaan",
"policyErrorCreate": "Error creating policy",
"policyErrorCreateDescription": "An error occurred when creating the policy",
"policyErrorCreateMessageDescription": "An unexpected error occurred",
"policyErrorUpdate": "Error updating policy",
"policyErrorUpdateDescription": "An error occurred when updating the policy",
"policyErrorUpdateMessageDescription": "An unexpected error occurred",
"policyCreatedSuccess": "Resource policy succesfully created",
"policyUpdatedSuccess": "Resource policy succesfully updated",
"authMethodsSave": "Save auth methods",
"rulesSave": "Save Rules",
"resourceErrorCreate": "Fout bij maken document",
"resourceErrorCreateDescription": "Er is een fout opgetreden bij het maken van het document",
"resourceErrorCreateMessage": "Fout bij maken bron:",
@@ -810,6 +868,16 @@
"pincodeAdd": "PIN-code toevoegen",
"pincodeRemove": "PIN-code verwijderen",
"resourceAuthMethods": "Authenticatie methoden",
"resourcePolicyAuthMethodsEmpty": "No authentication method",
"resourcePolicyOtpEmpty": "No one time password",
"resourcePolicyReadOnly": "This policy is Read only",
"resourcePolicyReadOnlyDescription": "This resource policy is shared accross multiple resources, you cannot edit it on this page.",
"resourcePolicyTypeSave": "Save Resource type",
"resourcePolicySelect": "Select resource policy",
"resourcePolicySelectError": "Select a resource policy",
"resourcePolicyNotFound": "Policy not found",
"resourcePolicySearch": "Search policies",
"resourcePolicyRulesEmpty": "No authentication rules",
"resourceAuthMethodsDescriptions": "Sta toegang tot de bron toe via extra autorisatiemethoden",
"resourceAuthSettingsSave": "Succesvol opgeslagen",
"resourceAuthSettingsSaveDescription": "Verificatie-instellingen zijn opgeslagen",
@@ -845,6 +913,12 @@
"resourcePincodeSetupTitle": "Pincode instellen",
"resourcePincodeSetupTitleDescription": "Stel een pincode in om deze hulpbron te beschermen",
"resourceRoleDescription": "Beheerders hebben altijd toegang tot deze bron.",
"resourcePolicySelectTitle": "Resource Access Policy",
"resourcePolicySelectDescription": "Select the resource policy type for authentication",
"resourcePolicyInline": "Inline Resource Policy",
"resourcePolicyInlineDescription": "Access Policy scoped to only this resource",
"resourcePolicyShared": "Shared Resource Policy",
"resourcePolicySharedDescription": "Access Policy shared accross multiple resources",
"resourceUsersRoles": "Toegang Bediening",
"resourceUsersRolesDescription": "Configureer welke gebruikers en rollen deze pagina kunnen bezoeken",
"resourceUsersRolesSubmit": "Bewaar Toegangsbesturing",
@@ -1140,6 +1214,18 @@
"idpErrorConnectingTo": "Er was een probleem bij het verbinden met {name}. Neem contact op met uw beheerder.",
"idpErrorNotFound": "IdP niet gevonden",
"inviteInvalid": "Ongeldige uitnodiging",
"labels": "Labels",
"orgLabelsDescription": "Manage labels in this organization.",
"addLabels": "Add labels",
"siteLabelsTab": "Labels",
"siteLabelsDescription": "Manage labels associated with this site.",
"labelsNotFound": "Labels not found",
"labelSearch": "Search labels",
"accessLabelFilterCount": "{count, plural, one {# label} other {# labels}}",
"labelOverflowCount": "+{count, plural, one {# label} other {# labels}}",
"accessLabelFilterClear": "Clear label filters",
"selectColor": "Select color",
"createNewLabel": "Create new org label \"{label}\"",
"inviteInvalidDescription": "Uitnodigingslink is ongeldig.",
"inviteErrorWrongUser": "Uitnodiging is niet voor deze gebruiker",
"inviteErrorUserNotExists": "Gebruiker bestaat niet. Maak eerst een account aan.",
@@ -1374,6 +1460,8 @@
"sidebarResources": "Bronnen",
"sidebarProxyResources": "Openbaar",
"sidebarClientResources": "Privé",
"sidebarPolicies": "Policies",
"sidebarResourcePolicies": "Resources",
"sidebarAccessControl": "Toegangs controle",
"sidebarLogsAndAnalytics": "Logs & Analytics",
"sidebarTeam": "Team",
@@ -1557,7 +1645,8 @@
"standaloneHcFilterSiteIdFallback": "Site {id}",
"standaloneHcFilterResourceIdFallback": "Bron {id}",
"blueprints": "Blauwdrukken",
"blueprintsDescription": "Gebruik declaratieve configuraties en bekijk vorige uitvoeringen.",
"blueprintsLog": "Blueprints Log",
"blueprintsDescription": "View past blueprint applications and their results",
"blueprintAdd": "Blauwdruk toevoegen",
"blueprintGoBack": "Bekijk alle Blauwdrukken",
"blueprintCreate": "Creëer blauwdruk",
@@ -1575,7 +1664,17 @@
"contents": "Inhoud",
"parsedContents": "Geparseerde inhoud (alleen lezen)",
"enableDockerSocket": "Schakel Docker Blauwdruk in",
"enableDockerSocketDescription": "Schakel Docker Socket label in voor blauwdruk labels. Pad naar Nieuw.",
"enableDockerSocketDescription": "Enable Docker Socket label scraping for blueprint labels. Socket path must be provided to the site connector. Read about how this works in <docsLink>the documentation</docsLink>.",
"newtAutoUpdate": "Enable Site Auto-Update",
"newtAutoUpdateDescription": "When enabled, site connectors will automatically update to the latest version when a new release is available.",
"siteAutoUpdate": "Site Auto-Update",
"siteAutoUpdateLabel": "Enable Auto-Update",
"siteAutoUpdateDescription": "Control whether this site's connector automatically downloads the latest version.",
"siteAutoUpdateOrgDefault": "Organization default: {state}",
"siteAutoUpdateOverriding": "Overriding organization setting",
"siteAutoUpdateResetToOrg": "Reset to Organization Default",
"siteAutoUpdateEnabled": "enabled",
"siteAutoUpdateDisabled": "disabled",
"viewDockerContainers": "Bekijk Docker containers",
"containersIn": "Containers in {siteName}",
"selectContainerDescription": "Selecteer een container om als hostnaam voor dit doel te gebruiken. Klik op een poort om een poort te gebruiken.",
@@ -1620,6 +1719,7 @@
"certificateStatus": "Certificaat",
"certificateStatusAutoRefreshHint": "Status ververst automatisch.",
"loading": "Bezig met laden",
"loadingEllipsis": "Loading...",
"loadingAnalytics": "Laden van Analytics",
"restart": "Herstarten",
"domains": "Domeinen",
@@ -1846,6 +1946,7 @@
"billingManageLicenseSubscription": "Beheer je abonnement voor betaalde zelf gehoste licentiesleutels",
"billingCurrentKeys": "Huidige toetsen",
"billingModifyCurrentPlan": "Huidig plan wijzigen",
"billingManageLicenseSubscriptionDescription": "Manage your subscription for paid self-hosted license keys and download invoices.",
"billingConfirmUpgrade": "Bevestig Upgrade",
"billingConfirmDowngrade": "Downgraden bevestigen",
"billingConfirmUpgradeDescription": "U staat op het punt uw abonnement te upgraden. Controleer de nieuwe limieten en prijzen hieronder.",
@@ -1943,7 +2044,36 @@
"timeIsInSeconds": "Tijd is in seconden",
"requireDeviceApproval": "Vereist goedkeuring van apparaat",
"requireDeviceApprovalDescription": "Gebruikers met deze rol hebben nieuwe apparaten nodig die door een beheerder zijn goedgekeurd voordat ze verbinding kunnen maken met bronnen en deze kunnen gebruiken.",
"sshAccess": "SSH toegang",
"sshSettings": "SSH Settings",
"rdpSettings": "RDP Settings",
"vncSettings": "VNC Settings",
"sshServer": "SSH Server",
"rdpServer": "RDP Server",
"vncServer": "VNC Server",
"sshServerDescription": "Set up the authentication method, daemon location, and server destination",
"rdpServerDescription": "Configure the destination and port of the RDP server",
"vncServerDescription": "Configure the destination and port of the VNC server",
"sshServerMode": "Mode",
"sshServerModeStandard": "Standard SSH Server",
"sshServerModePangolin": "Pangolin SSH",
"sshServerModeStandardDescription": "Routes commands over network to an SSH server such as OpenSSH.",
"sshServerModeNative": "Native SSH Server",
"sshServerModeNativeDescription": "Executes commands directly on the host via the Site Connector. No network config required.",
"sshAuthenticationMethod": "Authentication Method",
"sshAuthMethodManual": "Manual Authentication",
"sshAuthMethodManualDescription": "Requires existing host credentials. Bypasses automatic provisioning.",
"sshAuthMethodAutomated": "Automated Provisioning",
"sshAuthMethodAutomatedDescription": "Automatically creates users, groups, and sudo permissions on host.",
"sshAuthDaemonLocation": "Auth Daemon Location",
"sshDaemonLocationSiteDescription": "Executes locally on the machine hosting the site connector.",
"sshDaemonLocationRemote": "On Remote Host",
"sshDaemonLocationRemoteDescription": "Executes on a separate target machine on the same network.",
"sshDaemonDisclaimer": "Ensure your target host is properly configured to run the auth daemon before completing this setup, or provisioning will fail.",
"sshDaemonPort": "Daemon Port",
"sshServerDestination": "Server Destination",
"sshServerDestinationDescription": "Configure the destination and port of the SSH server",
"destination": "Destination",
"bgTargetMultiSiteDisclaimer": "Selecting multiple sites enables resilient routing and failover for high availability.",
"roleAllowSsh": "SSH toestaan",
"roleAllowSshAllow": "Toestaan",
"roleAllowSshDisallow": "Weigeren",
@@ -1957,7 +2087,7 @@
"sshSudoModeCommandsDescription": "Gebruiker kan alleen de opgegeven commando's uitvoeren met de sudo.",
"sshSudo": "sudo toestaan",
"sshSudoCommands": "Sudo Commando's",
"sshSudoCommandsDescription": "Komma's gescheiden lijst van commando's waar de gebruiker een sudo mee mag uitvoeren.",
"sshSudoCommandsDescription": "Comma separated list of commands the user is allowed to run with sudo. Absolute paths must be used.",
"sshCreateHomeDir": "Maak Home Directory",
"sshUnixGroups": "Unix groepen",
"sshUnixGroupsDescription": "Door komma's gescheiden Unix-groepen om de gebruiker toe te voegen aan de doelhost.",
@@ -2049,8 +2179,9 @@
"editInternalResourceDialogModeCidr": "CIDR",
"editInternalResourceDialogModeHttp": "HTTP",
"editInternalResourceDialogModeHttps": "HTTPS",
"editInternalResourceDialogModeSsh": "SSH",
"editInternalResourceDialogScheme": "Schema",
"editInternalResourceDialogEnableSsl": "TLS inschakelen",
"editInternalResourceDialogEnableSsl": "Enable TLS",
"editInternalResourceDialogEnableSslDescription": "Schakel SSL/TLS-encryptie in voor beveiligde HTTPS-verbindingen met de bestemming.",
"editInternalResourceDialogDestination": "Bestemming",
"editInternalResourceDialogDestinationHostDescription": "Het IP-adres of de hostnaam van de bron op het netwerk van de site.",
@@ -2098,9 +2229,10 @@
"createInternalResourceDialogModeCidr": "CIDR",
"createInternalResourceDialogModeHttp": "HTTP",
"createInternalResourceDialogModeHttps": "HTTPS",
"createInternalResourceDialogModeSsh": "SSH",
"scheme": "Schema",
"createInternalResourceDialogScheme": "Schema",
"createInternalResourceDialogEnableSsl": "TLS inschakelen",
"createInternalResourceDialogEnableSsl": "Enable TLS",
"createInternalResourceDialogEnableSslDescription": "Schakel SSL/TLS-encryptie in voor beveiligde HTTPS-verbindingen met de bestemming.",
"createInternalResourceDialogDestination": "Bestemming",
"createInternalResourceDialogDestinationHostDescription": "Het IP-adres of de hostnaam van de bron op het netwerk van de site.",
@@ -2233,7 +2365,7 @@
"description": "betrouwbaardere en slecht onderhouden Pangolin server met extra klokken en klokkenluiders",
"introTitle": "Beheerde zelfgehoste pangolin",
"introDescription": "is een implementatieoptie ontworpen voor mensen die eenvoud en extra betrouwbaarheid willen, terwijl hun gegevens privé en zelf georganiseerd blijven.",
"introDetail": "Met deze optie beheert u nog steeds uw eigen Pangolin node - uw tunnels, TLS-verbinding en verkeer alles op uw server. Het verschil is dat beheer en monitoring worden behandeld via onze cloud dashboard, wat een aantal voordelen oplevert:",
"introDetail": "With this option, you still run your own Pangolin node - your tunnels, TLS termination, and traffic all stay on your server. The difference is that management and monitoring are handled through our cloud dashboard, which unlocks a number of benefits:",
"benefitSimplerOperations": {
"title": "Simpler operaties",
"description": "Je hoeft geen eigen mailserver te draaien of complexe waarschuwingen in te stellen. Je krijgt gezondheidscontroles en downtime meldingen uit de box."
@@ -2937,7 +3069,7 @@
"learnMore": "Meer informatie",
"backToHome": "Ga terug naar startpagina",
"needToSignInToOrg": "Moet u de identiteit provider van uw organisatie gebruiken?",
"maintenanceMode": "Onderhoudsmodus",
"maintenanceMode": "Maintenance Page",
"maintenanceModeDescription": "Toon een onderhoudspagina aan bezoekers",
"maintenanceModeType": "Type onderhoudsmodus",
"showMaintenancePage": "Toon een onderhoudspagina aan bezoekers",
@@ -2967,6 +3099,7 @@
"maintenanceScreenEstimatedCompletion": "Geschatte voltooiing:",
"createInternalResourceDialogDestinationRequired": "Bestemming is vereist",
"available": "Beschikbaar",
"disabledResourceDescription": "When disabled, the resource will be inaccessible by everyone.",
"archived": "Gearchiveerd",
"noArchivedDevices": "Geen gearchiveerde apparaten gevonden",
"deviceArchived": "Apparaat gearchiveerd",
@@ -3296,5 +3429,6 @@
"memberPortalResourceDisabled": "Bron Uitgeschakeld",
"memberPortalShowingResources": "Toont {start}-{end} van {total} bronnen",
"memberPortalPrevious": "Vorige",
"memberPortalNext": "Volgende"
"memberPortalNext": "Volgende",
"httpSettings": "HTTP Settings"
}

View File

@@ -176,6 +176,7 @@
"shareErrorCreateDescription": "Wystąpił błąd podczas tworzenia linku udostępniania",
"shareCreateDescription": "Każdy z tym linkiem może uzyskać dostęp do zasobu",
"shareTitleOptional": "Tytuł (opcjonalnie)",
"sharePathOptional": "Path (optional)",
"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.",
@@ -208,11 +209,33 @@
"resourcesSearch": "Szukaj zasobów...",
"resourceAdd": "Dodaj zasób",
"resourceErrorDelte": "Błąd podczas usuwania zasobu",
"resourcePoliciesTitle": "Manage Resource Policies",
"resourcePoliciesAttachedResourcesColumnTitle": "Attached resources",
"resourcePoliciesAttachedResources": "{count} resource(s)",
"resourcePoliciesAttachedResourcesEmpty": "no resources",
"resourcePoliciesDescription": "Create and manage authentication policies to control access to your resources",
"resourcePoliciesSearch": "Search policies...",
"resourcePoliciesAdd": "Add Policy",
"resourcePoliciesDefaultBadgeText": "Default policy",
"resourcePoliciesCreate": "Create Resource Policy",
"resourcePoliciesCreateDescription": "Follow the steps below to create a new policy",
"resourcePolicyName": "Policy Name",
"resourcePolicyNameDescription": "Give this policy a name to identify it across your resources",
"resourcePolicyNamePlaceholder": "e.g. Internal Access Policy",
"resourcePoliciesSeeAll": "See All Policies",
"resourcePolicyAuthMethodAdd": "Add Authentication Method",
"resourcePolicyOtpEmailAdd": "Add OTP emails",
"resourcePolicyRulesAdd": "Add Rules",
"resourcePolicyAuthMethodsDescription": "Allow access to resources via additional auth methods",
"resourcePolicyUsersRolesDescription": "Configure which users and roles can visit associated resources",
"rulesResourcePolicyDescription": "Configure rules to control access resources associated to this policy",
"authentication": "Uwierzytelnianie",
"protected": "Chronione",
"notProtected": "Niechronione",
"resourceMessageRemove": "Po usunięciu zasób nie będzie już dostępny. Wszystkie cele związane z zasobem zostaną również usunięte.",
"resourceQuestionRemove": "Czy na pewno chcesz usunąć zasób z organizacji?",
"resourcePolicyMessageRemove": "Once removed, the resource policy will no longer be accessible. All resources associated with the resource will be unlinked and left without authentication.",
"resourcePolicyQuestionRemove": "Are you sure you want to remove the resource policy from the organization?",
"resourceHTTP": "Zasób HTTPS",
"resourceHTTPDescription": "Proxy zapytań przez HTTPS przy użyciu w pełni kwalifikowanej nazwy domeny.",
"resourceRaw": "Surowy zasób TCP/UDP",
@@ -220,8 +243,9 @@
"resourceRawDescriptionCloud": "Żądania proxy nad surowym TCP/UDP przy użyciu numeru portu. Wymaga stron aby połączyć się ze zdalnym węzłem.",
"resourceCreate": "Utwórz zasób",
"resourceCreateDescription": "Wykonaj poniższe kroki, aby utworzyć nowy zasób",
"resourceCreateGeneralDescription": "Configure the basic resource settings including the name and the type",
"resourceSeeAll": "Zobacz wszystkie zasoby",
"resourceInfo": "Informacje o zasobach",
"resourceCreateGeneral": "General",
"resourceNameDescription": "To jest wyświetlana nazwa zasobu.",
"siteSelect": "Wybierz witrynę",
"siteSearch": "Szukaj witryny",
@@ -231,12 +255,15 @@
"noCountryFound": "Nie znaleziono kraju.",
"siteSelectionDescription": "Ta strona zapewni połączenie z celem.",
"resourceType": "Typ zasobu",
"resourceTypeDescription": "Określ jak uzyskać dostęp do zasobu",
"resourceTypeDescription": "This controls the resource protocol and how it will be rendered in the browser. This cant be changed later.",
"resourceDomainDescription": "The resource will be served at this fully qualified domain name.",
"resourceHTTPSSettings": "Ustawienia HTTPS",
"resourceHTTPSSettingsDescription": "Skonfiguruj jak zasób będzie dostępny przez HTTPS",
"resourcePortDescription": "The external port on the Pangolin instance or node where the resource will be accessible.",
"domainType": "Typ domeny",
"subdomain": "Poddomena",
"baseDomain": "Bazowa domena",
"configure": "Configure",
"subdomnainDescription": "Poddomena, w której zasób będzie dostępny.",
"resourceRawSettings": "Ustawienia TCP/UDP",
"resourceRawSettingsDescription": "Skonfiguruj jak zasób będzie dostępny przez TCP/UDP",
@@ -253,8 +280,27 @@
"resourceLearnRaw": "Dowiedz się, jak skonfigurować zasoby TCP/UDP",
"resourceBack": "Powrót do zasobów",
"resourceGoTo": "Przejdź do zasobu",
"resourcePolicyDelete": "Delete Resource Policy",
"resourcePolicyDeleteConfirm": "Confirm Delete Resource Policy",
"resourceDelete": "Usuń zasób",
"resourceDeleteConfirm": "Potwierdź usunięcie zasobu",
"labelDelete": "Delete Label",
"labelAdd": "Add Label",
"labelCreateSuccessMessage": "Label Created Successfully",
"labelEditSuccessMessage": "Label Modified Successfully",
"labelNameField": "Label Name",
"labelColorField": "Label Color",
"labelPlaceholder": "Ex: homelab",
"labelCreate": "Create Label",
"createLabelDialogTitle": "Create Label",
"createLabelDialogDescription": "Create a new label that can be attached to this organization",
"labelEdit": "Edit Label",
"editLabelDialogTitle": "Update Label",
"editLabelDialogDescription": "Edit a new label that can be attached to this organization",
"labelDeleteConfirm": "Confirm Delete Label",
"labelErrorDelete": "Failed to delete label",
"labelMessageRemove": "This action is permanent. All sites, resources, and clients tagged with this label will be untagged.",
"labelQuestionRemove": "Are you sure you want to remove the label from the organization?",
"visibility": "Widoczność",
"enabled": "Włączone",
"disabled": "Wyłączone",
@@ -265,6 +311,8 @@
"rules": "Regulamin",
"resourceSettingDescription": "Skonfiguruj ustawienia zasobu",
"resourceSetting": "Ustawienia {resourceName}",
"resourcePolicySettingDescription": "Configure the settings on the resource policy",
"resourcePolicySetting": "{policyName} Settings",
"alwaysAllow": "Omijanie uwierzytelniania",
"alwaysDeny": "Blokuj dostęp",
"passToAuth": "Przekaż do Autoryzacji",
@@ -630,7 +678,7 @@
"createdAt": "Utworzono",
"proxyErrorInvalidHeader": "Nieprawidłowa wartość niestandardowego nagłówka hosta. Użyj formatu nazwy domeny lub zapisz pusty, aby usunąć niestandardowy nagłówek hosta.",
"proxyErrorTls": "Nieprawidłowa nazwa serwera TLS. Użyj formatu nazwy domeny lub zapisz pusty, aby usunąć nazwę serwera TLS.",
"proxyEnableSSL": "Włącz TLS",
"proxyEnableSSL": "Enable TLS",
"proxyEnableSSLDescription": "Włącz szyfrowanie SSL/TLS dla bezpiecznych połączeń HTTPS z celami.",
"target": "Target",
"configureTarget": "Konfiguruj Targety",
@@ -747,6 +795,16 @@
"rulesNoOne": "Brak reguł. Dodaj regułę używając formularza.",
"rulesOrder": "Reguły są oceniane według priorytetu w kolejności rosnącej.",
"rulesSubmit": "Zapisz reguły",
"policyErrorCreate": "Error creating policy",
"policyErrorCreateDescription": "An error occurred when creating the policy",
"policyErrorCreateMessageDescription": "An unexpected error occurred",
"policyErrorUpdate": "Error updating policy",
"policyErrorUpdateDescription": "An error occurred when updating the policy",
"policyErrorUpdateMessageDescription": "An unexpected error occurred",
"policyCreatedSuccess": "Resource policy succesfully created",
"policyUpdatedSuccess": "Resource policy succesfully updated",
"authMethodsSave": "Save auth methods",
"rulesSave": "Save Rules",
"resourceErrorCreate": "Błąd podczas tworzenia zasobu",
"resourceErrorCreateDescription": "Wystąpił błąd podczas tworzenia zasobu",
"resourceErrorCreateMessage": "Błąd podczas tworzenia zasobu:",
@@ -810,6 +868,16 @@
"pincodeAdd": "Dodaj kod PIN",
"pincodeRemove": "Usuń kod PIN",
"resourceAuthMethods": "Metody uwierzytelniania",
"resourcePolicyAuthMethodsEmpty": "No authentication method",
"resourcePolicyOtpEmpty": "No one time password",
"resourcePolicyReadOnly": "This policy is Read only",
"resourcePolicyReadOnlyDescription": "This resource policy is shared accross multiple resources, you cannot edit it on this page.",
"resourcePolicyTypeSave": "Save Resource type",
"resourcePolicySelect": "Select resource policy",
"resourcePolicySelectError": "Select a resource policy",
"resourcePolicyNotFound": "Policy not found",
"resourcePolicySearch": "Search policies",
"resourcePolicyRulesEmpty": "No authentication rules",
"resourceAuthMethodsDescriptions": "Zezwól na dostęp do zasobu przez dodatkowe metody uwierzytelniania",
"resourceAuthSettingsSave": "Zapisano pomyślnie",
"resourceAuthSettingsSaveDescription": "Ustawienia uwierzytelniania zostały zapisane",
@@ -845,6 +913,12 @@
"resourcePincodeSetupTitle": "Ustaw kod PIN",
"resourcePincodeSetupTitleDescription": "Ustaw kod PIN, aby chronić ten zasób",
"resourceRoleDescription": "Administratorzy zawsze mają dostęp do tego zasobu.",
"resourcePolicySelectTitle": "Resource Access Policy",
"resourcePolicySelectDescription": "Select the resource policy type for authentication",
"resourcePolicyInline": "Inline Resource Policy",
"resourcePolicyInlineDescription": "Access Policy scoped to only this resource",
"resourcePolicyShared": "Shared Resource Policy",
"resourcePolicySharedDescription": "Access Policy shared accross multiple resources",
"resourceUsersRoles": "Kontrola dostępu",
"resourceUsersRolesDescription": "Skonfiguruj, którzy użytkownicy i role mogą odwiedzać ten zasób",
"resourceUsersRolesSubmit": "Zapisz kontrole dostępu",
@@ -1140,6 +1214,18 @@
"idpErrorConnectingTo": "Wystąpił problem z połączeniem z {name}. Skontaktuj się z administratorem.",
"idpErrorNotFound": "Nie znaleziono IdP",
"inviteInvalid": "Nieprawidłowe zaproszenie",
"labels": "Labels",
"orgLabelsDescription": "Manage labels in this organization.",
"addLabels": "Add labels",
"siteLabelsTab": "Labels",
"siteLabelsDescription": "Manage labels associated with this site.",
"labelsNotFound": "Labels not found",
"labelSearch": "Search labels",
"accessLabelFilterCount": "{count, plural, one {# label} other {# labels}}",
"labelOverflowCount": "+{count, plural, one {# label} other {# labels}}",
"accessLabelFilterClear": "Clear label filters",
"selectColor": "Select color",
"createNewLabel": "Create new org label \"{label}\"",
"inviteInvalidDescription": "Link zapraszający jest nieprawidłowy.",
"inviteErrorWrongUser": "Zaproszenie nie jest dla tego użytkownika",
"inviteErrorUserNotExists": "Użytkownik nie istnieje. Najpierw utwórz konto.",
@@ -1374,6 +1460,8 @@
"sidebarResources": "Zasoby",
"sidebarProxyResources": "Publiczne",
"sidebarClientResources": "Prywatny",
"sidebarPolicies": "Policies",
"sidebarResourcePolicies": "Resources",
"sidebarAccessControl": "Kontrola dostępu",
"sidebarLogsAndAnalytics": "Logi i Analityki",
"sidebarTeam": "Drużyna",
@@ -1557,7 +1645,8 @@
"standaloneHcFilterSiteIdFallback": "Witryna {id}",
"standaloneHcFilterResourceIdFallback": "Zasób {id}",
"blueprints": "Schematy",
"blueprintsDescription": "Zastosuj konfiguracje deklaracyjne i wyświetl poprzednie operacje",
"blueprintsLog": "Blueprints Log",
"blueprintsDescription": "View past blueprint applications and their results",
"blueprintAdd": "Dodaj schemat",
"blueprintGoBack": "Zobacz wszystkie schematy",
"blueprintCreate": "Utwórz schemat",
@@ -1575,7 +1664,17 @@
"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.",
"enableDockerSocketDescription": "Enable Docker Socket label scraping for blueprint labels. Socket path must be provided to the site connector. Read about how this works in <docsLink>the documentation</docsLink>.",
"newtAutoUpdate": "Enable Site Auto-Update",
"newtAutoUpdateDescription": "When enabled, site connectors will automatically update to the latest version when a new release is available.",
"siteAutoUpdate": "Site Auto-Update",
"siteAutoUpdateLabel": "Enable Auto-Update",
"siteAutoUpdateDescription": "Control whether this site's connector automatically downloads the latest version.",
"siteAutoUpdateOrgDefault": "Organization default: {state}",
"siteAutoUpdateOverriding": "Overriding organization setting",
"siteAutoUpdateResetToOrg": "Reset to Organization Default",
"siteAutoUpdateEnabled": "enabled",
"siteAutoUpdateDisabled": "disabled",
"viewDockerContainers": "Zobacz kontenery dokujące",
"containersIn": "Pojemniki w {siteName}",
"selectContainerDescription": "Wybierz dowolny kontener do użycia jako nazwa hosta dla tego celu. Kliknij port, aby użyć portu.",
@@ -1620,6 +1719,7 @@
"certificateStatus": "Certyfikat",
"certificateStatusAutoRefreshHint": "Status odświeża się automatycznie.",
"loading": "Ładowanie",
"loadingEllipsis": "Loading...",
"loadingAnalytics": "Ładowanie Analityki",
"restart": "Uruchom ponownie",
"domains": "Domeny",
@@ -1846,6 +1946,7 @@
"billingManageLicenseSubscription": "Zarządzaj subskrypcją płatnych własnych kluczy licencyjnych",
"billingCurrentKeys": "Bieżące klucze",
"billingModifyCurrentPlan": "Modyfikuj bieżący plan",
"billingManageLicenseSubscriptionDescription": "Manage your subscription for paid self-hosted license keys and download invoices.",
"billingConfirmUpgrade": "Potwierdź aktualizację",
"billingConfirmDowngrade": "Potwierdź obniżenie",
"billingConfirmUpgradeDescription": "Zamierzasz ulepszyć swój plan. Przejrzyj nowe limity i ceny poniżej.",
@@ -1943,7 +2044,36 @@
"timeIsInSeconds": "Czas w sekundach",
"requireDeviceApproval": "Wymagaj zatwierdzenia urządzenia",
"requireDeviceApprovalDescription": "Użytkownicy o tej roli potrzebują nowych urządzeń zatwierdzonych przez administratora, zanim będą mogli połączyć się i uzyskać dostęp do zasobów.",
"sshAccess": "Dostęp SSH",
"sshSettings": "SSH Settings",
"rdpSettings": "RDP Settings",
"vncSettings": "VNC Settings",
"sshServer": "SSH Server",
"rdpServer": "RDP Server",
"vncServer": "VNC Server",
"sshServerDescription": "Set up the authentication method, daemon location, and server destination",
"rdpServerDescription": "Configure the destination and port of the RDP server",
"vncServerDescription": "Configure the destination and port of the VNC server",
"sshServerMode": "Mode",
"sshServerModeStandard": "Standard SSH Server",
"sshServerModePangolin": "Pangolin SSH",
"sshServerModeStandardDescription": "Routes commands over network to an SSH server such as OpenSSH.",
"sshServerModeNative": "Native SSH Server",
"sshServerModeNativeDescription": "Executes commands directly on the host via the Site Connector. No network config required.",
"sshAuthenticationMethod": "Authentication Method",
"sshAuthMethodManual": "Manual Authentication",
"sshAuthMethodManualDescription": "Requires existing host credentials. Bypasses automatic provisioning.",
"sshAuthMethodAutomated": "Automated Provisioning",
"sshAuthMethodAutomatedDescription": "Automatically creates users, groups, and sudo permissions on host.",
"sshAuthDaemonLocation": "Auth Daemon Location",
"sshDaemonLocationSiteDescription": "Executes locally on the machine hosting the site connector.",
"sshDaemonLocationRemote": "On Remote Host",
"sshDaemonLocationRemoteDescription": "Executes on a separate target machine on the same network.",
"sshDaemonDisclaimer": "Ensure your target host is properly configured to run the auth daemon before completing this setup, or provisioning will fail.",
"sshDaemonPort": "Daemon Port",
"sshServerDestination": "Server Destination",
"sshServerDestinationDescription": "Configure the destination and port of the SSH server",
"destination": "Destination",
"bgTargetMultiSiteDisclaimer": "Selecting multiple sites enables resilient routing and failover for high availability.",
"roleAllowSsh": "Zezwalaj na SSH",
"roleAllowSshAllow": "Zezwól",
"roleAllowSshDisallow": "Nie zezwalaj",
@@ -1957,7 +2087,7 @@
"sshSudoModeCommandsDescription": "Użytkownik może uruchamiać tylko określone polecenia z sudo.",
"sshSudo": "Zezwól na sudo",
"sshSudoCommands": "Komendy Sudo",
"sshSudoCommandsDescription": "Lista poleceń oddzielonych przecinkami, które użytkownik może uruchamiać z sudo.",
"sshSudoCommandsDescription": "Comma separated list of commands the user is allowed to run with sudo. Absolute paths must be used.",
"sshCreateHomeDir": "Utwórz katalog domowy",
"sshUnixGroups": "Grupy Unix",
"sshUnixGroupsDescription": "Oddzielone przecinkami grupy Unix, aby dodać użytkownika do docelowego hosta.",
@@ -2049,8 +2179,9 @@
"editInternalResourceDialogModeCidr": "CIDR",
"editInternalResourceDialogModeHttp": "HTTP",
"editInternalResourceDialogModeHttps": "HTTPS",
"editInternalResourceDialogModeSsh": "SSH",
"editInternalResourceDialogScheme": "Schemat",
"editInternalResourceDialogEnableSsl": "Włącz TLS",
"editInternalResourceDialogEnableSsl": "Enable TLS",
"editInternalResourceDialogEnableSslDescription": "Włącz szyfrowanie SSL/TLS dla bezpiecznych połączeń HTTPS z miejscem docelowym.",
"editInternalResourceDialogDestination": "Miejsce docelowe",
"editInternalResourceDialogDestinationHostDescription": "Adres IP lub nazwa hosta zasobu w sieci witryny.",
@@ -2098,9 +2229,10 @@
"createInternalResourceDialogModeCidr": "CIDR",
"createInternalResourceDialogModeHttp": "HTTP",
"createInternalResourceDialogModeHttps": "HTTPS",
"createInternalResourceDialogModeSsh": "SSH",
"scheme": "Schemat",
"createInternalResourceDialogScheme": "Schemat",
"createInternalResourceDialogEnableSsl": "Włącz TLS",
"createInternalResourceDialogEnableSsl": "Enable TLS",
"createInternalResourceDialogEnableSslDescription": "Włącz szyfrowanie SSL/TLS dla bezpiecznych połączeń HTTPS z miejscem docelowym.",
"createInternalResourceDialogDestination": "Miejsce docelowe",
"createInternalResourceDialogDestinationHostDescription": "Adres IP lub nazwa hosta zasobu w sieci witryny.",
@@ -2233,7 +2365,7 @@
"description": "Większa niezawodność i niska konserwacja serwera Pangolin z dodatkowymi dzwonkami i sygnałami",
"introTitle": "Zarządzany samowystarczalny Pangolin",
"introDescription": "jest opcją wdrażania zaprojektowaną dla osób, które chcą prostoty i dodatkowej niezawodności, przy jednoczesnym utrzymaniu swoich danych prywatnych i samodzielnych.",
"introDetail": "Z tą opcją nadal obsługujesz swój własny węzeł Pangolin - tunele, zakończenie TLS i ruch na Twoim serwerze. Różnica polega na tym, że zarządzanie i monitorowanie odbywa się za pomocą naszej tablicy rozdzielczej, która odblokowuje szereg korzyści:",
"introDetail": "With this option, you still run your own Pangolin node - your tunnels, TLS termination, and traffic all stay on your server. The difference is that management and monitoring are handled through our cloud dashboard, which unlocks a number of benefits:",
"benefitSimplerOperations": {
"title": "Uproszczone operacje",
"description": "Nie ma potrzeby uruchamiania własnego serwera pocztowego lub ustawiania skomplikowanych powiadomień. Będziesz mieć kontrolę zdrowia i powiadomienia o przestoju."
@@ -2937,7 +3069,7 @@
"learnMore": "Dowiedz się więcej",
"backToHome": "Wróć do strony głównej",
"needToSignInToOrg": "Czy potrzebujesz użyć dostawcy tożsamości organizacji?",
"maintenanceMode": "Tryb konserwacji",
"maintenanceMode": "Maintenance Page",
"maintenanceModeDescription": "Wyświetl stronę konserwacyjną odwiedzającym",
"maintenanceModeType": "Typ trybu konserwacji",
"showMaintenancePage": "Pokaż odwiedzającym stronę konserwacji",
@@ -2967,6 +3099,7 @@
"maintenanceScreenEstimatedCompletion": "Szacowane zakończenie:",
"createInternalResourceDialogDestinationRequired": "Miejsce docelowe jest wymagane",
"available": "Dostępny",
"disabledResourceDescription": "When disabled, the resource will be inaccessible by everyone.",
"archived": "Zarchiwizowane",
"noArchivedDevices": "Nie znaleziono zarchiwizowanych urządzeń",
"deviceArchived": "Urządzenie zarchiwizowane",
@@ -3296,5 +3429,6 @@
"memberPortalResourceDisabled": "Zasób wyłączony",
"memberPortalShowingResources": "Wyświetlanie zasobów od {start} do {end} z {total}",
"memberPortalPrevious": "Poprzedni",
"memberPortalNext": "Następny"
"memberPortalNext": "Następny",
"httpSettings": "HTTP Settings"
}

View File

@@ -176,6 +176,7 @@
"shareErrorCreateDescription": "Ocorreu um erro ao criar o link de compartilhamento",
"shareCreateDescription": "Qualquer um com este link pode aceder o recurso",
"shareTitleOptional": "Título (opcional)",
"sharePathOptional": "Path (optional)",
"expireIn": "Expira em",
"neverExpire": "Nunca expirar",
"shareExpireDescription": "Tempo de expiração é quanto tempo o link será utilizável e oferecerá acesso ao recurso. Após este tempo, o link não funcionará mais, e os utilizadores que usaram este link perderão acesso ao recurso.",
@@ -208,11 +209,33 @@
"resourcesSearch": "Procurar recursos...",
"resourceAdd": "Adicionar Recurso",
"resourceErrorDelte": "Erro ao apagar recurso",
"resourcePoliciesTitle": "Manage Resource Policies",
"resourcePoliciesAttachedResourcesColumnTitle": "Attached resources",
"resourcePoliciesAttachedResources": "{count} resource(s)",
"resourcePoliciesAttachedResourcesEmpty": "no resources",
"resourcePoliciesDescription": "Create and manage authentication policies to control access to your resources",
"resourcePoliciesSearch": "Search policies...",
"resourcePoliciesAdd": "Add Policy",
"resourcePoliciesDefaultBadgeText": "Default policy",
"resourcePoliciesCreate": "Create Resource Policy",
"resourcePoliciesCreateDescription": "Follow the steps below to create a new policy",
"resourcePolicyName": "Policy Name",
"resourcePolicyNameDescription": "Give this policy a name to identify it across your resources",
"resourcePolicyNamePlaceholder": "e.g. Internal Access Policy",
"resourcePoliciesSeeAll": "See All Policies",
"resourcePolicyAuthMethodAdd": "Add Authentication Method",
"resourcePolicyOtpEmailAdd": "Add OTP emails",
"resourcePolicyRulesAdd": "Add Rules",
"resourcePolicyAuthMethodsDescription": "Allow access to resources via additional auth methods",
"resourcePolicyUsersRolesDescription": "Configure which users and roles can visit associated resources",
"rulesResourcePolicyDescription": "Configure rules to control access resources associated to this policy",
"authentication": "Autenticação",
"protected": "Protegido",
"notProtected": "Não Protegido",
"resourceMessageRemove": "Uma vez removido, o recurso não estará mais acessível. Todos os alvos associados ao recurso também serão removidos.",
"resourceQuestionRemove": "Você tem certeza que deseja remover o recurso da organização?",
"resourcePolicyMessageRemove": "Once removed, the resource policy will no longer be accessible. All resources associated with the resource will be unlinked and left without authentication.",
"resourcePolicyQuestionRemove": "Are you sure you want to remove the resource policy from the organization?",
"resourceHTTP": "Recurso HTTPS",
"resourceHTTPDescription": "Proxies requests sobre HTTPS usando um nome de domínio totalmente qualificado.",
"resourceRaw": "Recurso TCP/UDP bruto",
@@ -220,8 +243,9 @@
"resourceRawDescriptionCloud": "Proxy solicita por TCP/UDP bruto usando um número de porta. Requer que sites se conectem a um nó remoto.",
"resourceCreate": "Criar Recurso",
"resourceCreateDescription": "Siga os passos abaixo para criar um novo recurso",
"resourceCreateGeneralDescription": "Configure the basic resource settings including the name and the type",
"resourceSeeAll": "Ver todos os recursos",
"resourceInfo": "Informação do recurso",
"resourceCreateGeneral": "General",
"resourceNameDescription": "Este é o nome de exibição para o recurso.",
"siteSelect": "Selecionar site",
"siteSearch": "Procurar no site",
@@ -231,12 +255,15 @@
"noCountryFound": "Nenhum país encontrado.",
"siteSelectionDescription": "Este site fornecerá conectividade ao destino.",
"resourceType": "Tipo de Recurso",
"resourceTypeDescription": "Determine como acessar o recurso",
"resourceTypeDescription": "This controls the resource protocol and how it will be rendered in the browser. This cant be changed later.",
"resourceDomainDescription": "The resource will be served at this fully qualified domain name.",
"resourceHTTPSSettings": "Configurações de HTTPS",
"resourceHTTPSSettingsDescription": "Configure como o recurso será acessado por HTTPS",
"resourcePortDescription": "The external port on the Pangolin instance or node where the resource will be accessible.",
"domainType": "Tipo de domínio",
"subdomain": "Subdomínio",
"baseDomain": "Domínio Base",
"configure": "Configure",
"subdomnainDescription": "O subdomínio onde o recurso será acessível.",
"resourceRawSettings": "Configurações TCP/UDP",
"resourceRawSettingsDescription": "Configurar como o recurso será acessado sobre TCP/UDP",
@@ -253,8 +280,27 @@
"resourceLearnRaw": "Aprenda como configurar os recursos TCP/UDP",
"resourceBack": "Voltar aos recursos",
"resourceGoTo": "Ir para o Recurso",
"resourcePolicyDelete": "Delete Resource Policy",
"resourcePolicyDeleteConfirm": "Confirm Delete Resource Policy",
"resourceDelete": "Excluir Recurso",
"resourceDeleteConfirm": "Confirmar que pretende apagar o recurso",
"labelDelete": "Delete Label",
"labelAdd": "Add Label",
"labelCreateSuccessMessage": "Label Created Successfully",
"labelEditSuccessMessage": "Label Modified Successfully",
"labelNameField": "Label Name",
"labelColorField": "Label Color",
"labelPlaceholder": "Ex: homelab",
"labelCreate": "Create Label",
"createLabelDialogTitle": "Create Label",
"createLabelDialogDescription": "Create a new label that can be attached to this organization",
"labelEdit": "Edit Label",
"editLabelDialogTitle": "Update Label",
"editLabelDialogDescription": "Edit a new label that can be attached to this organization",
"labelDeleteConfirm": "Confirm Delete Label",
"labelErrorDelete": "Failed to delete label",
"labelMessageRemove": "This action is permanent. All sites, resources, and clients tagged with this label will be untagged.",
"labelQuestionRemove": "Are you sure you want to remove the label from the organization?",
"visibility": "Visibilidade",
"enabled": "Ativado",
"disabled": "Desabilitado",
@@ -265,6 +311,8 @@
"rules": "Regras",
"resourceSettingDescription": "Configure as configurações do recurso",
"resourceSetting": "Configurações do {resourceName}",
"resourcePolicySettingDescription": "Configure the settings on the resource policy",
"resourcePolicySetting": "{policyName} Settings",
"alwaysAllow": "Autenticação de bypass",
"alwaysDeny": "Bloquear Acesso",
"passToAuth": "Passar para Autenticação",
@@ -630,7 +678,7 @@
"createdAt": "Criado Em",
"proxyErrorInvalidHeader": "Valor do cabeçalho Host personalizado inválido. Use o formato de nome de domínio ou salve vazio para remover o cabeçalho Host personalizado.",
"proxyErrorTls": "Nome do Servidor TLS inválido. Use o formato de nome de domínio ou salve vazio para remover o Nome do Servidor TLS.",
"proxyEnableSSL": "Habilitar TLS",
"proxyEnableSSL": "Enable TLS",
"proxyEnableSSLDescription": "Habilitar criptografia SSL/TLS para conexões HTTPS seguras aos alvos.",
"target": "Target",
"configureTarget": "Configurar Alvos",
@@ -747,6 +795,16 @@
"rulesNoOne": "Sem regras. Adicione uma regra usando o formulário.",
"rulesOrder": "As regras são avaliadas por prioridade em ordem ascendente.",
"rulesSubmit": "Guardar Regras",
"policyErrorCreate": "Error creating policy",
"policyErrorCreateDescription": "An error occurred when creating the policy",
"policyErrorCreateMessageDescription": "An unexpected error occurred",
"policyErrorUpdate": "Error updating policy",
"policyErrorUpdateDescription": "An error occurred when updating the policy",
"policyErrorUpdateMessageDescription": "An unexpected error occurred",
"policyCreatedSuccess": "Resource policy succesfully created",
"policyUpdatedSuccess": "Resource policy succesfully updated",
"authMethodsSave": "Save auth methods",
"rulesSave": "Save Rules",
"resourceErrorCreate": "Erro ao criar recurso",
"resourceErrorCreateDescription": "Ocorreu um erro ao criar o recurso",
"resourceErrorCreateMessage": "Erro ao criar recurso:",
@@ -810,6 +868,16 @@
"pincodeAdd": "Adicionar Código PIN",
"pincodeRemove": "Remover Código PIN",
"resourceAuthMethods": "Métodos de Autenticação",
"resourcePolicyAuthMethodsEmpty": "No authentication method",
"resourcePolicyOtpEmpty": "No one time password",
"resourcePolicyReadOnly": "This policy is Read only",
"resourcePolicyReadOnlyDescription": "This resource policy is shared accross multiple resources, you cannot edit it on this page.",
"resourcePolicyTypeSave": "Save Resource type",
"resourcePolicySelect": "Select resource policy",
"resourcePolicySelectError": "Select a resource policy",
"resourcePolicyNotFound": "Policy not found",
"resourcePolicySearch": "Search policies",
"resourcePolicyRulesEmpty": "No authentication rules",
"resourceAuthMethodsDescriptions": "Permitir acesso ao recurso via métodos de autenticação adicionais",
"resourceAuthSettingsSave": "Salvo com sucesso",
"resourceAuthSettingsSaveDescription": "As configurações de autenticação foram salvas",
@@ -845,6 +913,12 @@
"resourcePincodeSetupTitle": "Definir Código PIN",
"resourcePincodeSetupTitleDescription": "Defina um código PIN para proteger este recurso",
"resourceRoleDescription": "Administradores sempre podem aceder este recurso.",
"resourcePolicySelectTitle": "Resource Access Policy",
"resourcePolicySelectDescription": "Select the resource policy type for authentication",
"resourcePolicyInline": "Inline Resource Policy",
"resourcePolicyInlineDescription": "Access Policy scoped to only this resource",
"resourcePolicyShared": "Shared Resource Policy",
"resourcePolicySharedDescription": "Access Policy shared accross multiple resources",
"resourceUsersRoles": "Controlos de Acesso",
"resourceUsersRolesDescription": "Configure quais utilizadores e funções podem visitar este recurso",
"resourceUsersRolesSubmit": "Guardar Controlos de Acesso",
@@ -1140,6 +1214,18 @@
"idpErrorConnectingTo": "Ocorreu um problema ao ligar a {name}. Por favor, contacte o seu administrador.",
"idpErrorNotFound": "IdP não encontrado",
"inviteInvalid": "Convite Inválido",
"labels": "Labels",
"orgLabelsDescription": "Manage labels in this organization.",
"addLabels": "Add labels",
"siteLabelsTab": "Labels",
"siteLabelsDescription": "Manage labels associated with this site.",
"labelsNotFound": "Labels not found",
"labelSearch": "Search labels",
"accessLabelFilterCount": "{count, plural, one {# label} other {# labels}}",
"labelOverflowCount": "+{count, plural, one {# label} other {# labels}}",
"accessLabelFilterClear": "Clear label filters",
"selectColor": "Select color",
"createNewLabel": "Create new org label \"{label}\"",
"inviteInvalidDescription": "O link do convite é inválido.",
"inviteErrorWrongUser": "O convite não é para este utilizador",
"inviteErrorUserNotExists": "O utilizador não existe. Por favor, crie uma conta primeiro.",
@@ -1374,6 +1460,8 @@
"sidebarResources": "Recursos",
"sidebarProxyResources": "Público",
"sidebarClientResources": "Privado",
"sidebarPolicies": "Policies",
"sidebarResourcePolicies": "Resources",
"sidebarAccessControl": "Controle de Acesso",
"sidebarLogsAndAnalytics": "Registros e Análises",
"sidebarTeam": "Equipe",
@@ -1557,7 +1645,8 @@
"standaloneHcFilterSiteIdFallback": "Site {id}",
"standaloneHcFilterResourceIdFallback": "Recurso {id}",
"blueprints": "Diagramas",
"blueprintsDescription": "Aplicar configurações declarativas e ver execuções anteriores",
"blueprintsLog": "Blueprints Log",
"blueprintsDescription": "View past blueprint applications and their results",
"blueprintAdd": "Adicionar Diagrama",
"blueprintGoBack": "Ver todos os Diagramas",
"blueprintCreate": "Criar Diagrama",
@@ -1575,7 +1664,17 @@
"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.",
"enableDockerSocketDescription": "Enable Docker Socket label scraping for blueprint labels. Socket path must be provided to the site connector. Read about how this works in <docsLink>the documentation</docsLink>.",
"newtAutoUpdate": "Enable Site Auto-Update",
"newtAutoUpdateDescription": "When enabled, site connectors will automatically update to the latest version when a new release is available.",
"siteAutoUpdate": "Site Auto-Update",
"siteAutoUpdateLabel": "Enable Auto-Update",
"siteAutoUpdateDescription": "Control whether this site's connector automatically downloads the latest version.",
"siteAutoUpdateOrgDefault": "Organization default: {state}",
"siteAutoUpdateOverriding": "Overriding organization setting",
"siteAutoUpdateResetToOrg": "Reset to Organization Default",
"siteAutoUpdateEnabled": "enabled",
"siteAutoUpdateDisabled": "disabled",
"viewDockerContainers": "Ver contêineres Docker",
"containersIn": "Contêineres em {siteName}",
"selectContainerDescription": "Selecione qualquer contêiner para usar como hostname para este alvo. Clique em uma porta para usar uma porta.",
@@ -1620,6 +1719,7 @@
"certificateStatus": "Certificado",
"certificateStatusAutoRefreshHint": "Status atualiza automaticamente.",
"loading": "Carregando",
"loadingEllipsis": "Loading...",
"loadingAnalytics": "Carregando Analytics",
"restart": "Reiniciar",
"domains": "Domínios",
@@ -1846,6 +1946,7 @@
"billingManageLicenseSubscription": "Gerencie sua assinatura para as chaves de licenças auto-hospedadas pagas",
"billingCurrentKeys": "Chaves atuais",
"billingModifyCurrentPlan": "Modificar o Plano Atual",
"billingManageLicenseSubscriptionDescription": "Manage your subscription for paid self-hosted license keys and download invoices.",
"billingConfirmUpgrade": "Confirmar a atualização",
"billingConfirmDowngrade": "Confirmar downgrade",
"billingConfirmUpgradeDescription": "Você está prestes a atualizar seu plano. Revise os novos limites e preços abaixo.",
@@ -1943,7 +2044,36 @@
"timeIsInSeconds": "O tempo está em segundos",
"requireDeviceApproval": "Exigir aprovação do dispositivo",
"requireDeviceApprovalDescription": "Usuários com esta função precisam de novos dispositivos aprovados por um administrador antes que eles possam se conectar e acessar recursos.",
"sshAccess": "Acesso SSH",
"sshSettings": "SSH Settings",
"rdpSettings": "RDP Settings",
"vncSettings": "VNC Settings",
"sshServer": "SSH Server",
"rdpServer": "RDP Server",
"vncServer": "VNC Server",
"sshServerDescription": "Set up the authentication method, daemon location, and server destination",
"rdpServerDescription": "Configure the destination and port of the RDP server",
"vncServerDescription": "Configure the destination and port of the VNC server",
"sshServerMode": "Mode",
"sshServerModeStandard": "Standard SSH Server",
"sshServerModePangolin": "Pangolin SSH",
"sshServerModeStandardDescription": "Routes commands over network to an SSH server such as OpenSSH.",
"sshServerModeNative": "Native SSH Server",
"sshServerModeNativeDescription": "Executes commands directly on the host via the Site Connector. No network config required.",
"sshAuthenticationMethod": "Authentication Method",
"sshAuthMethodManual": "Manual Authentication",
"sshAuthMethodManualDescription": "Requires existing host credentials. Bypasses automatic provisioning.",
"sshAuthMethodAutomated": "Automated Provisioning",
"sshAuthMethodAutomatedDescription": "Automatically creates users, groups, and sudo permissions on host.",
"sshAuthDaemonLocation": "Auth Daemon Location",
"sshDaemonLocationSiteDescription": "Executes locally on the machine hosting the site connector.",
"sshDaemonLocationRemote": "On Remote Host",
"sshDaemonLocationRemoteDescription": "Executes on a separate target machine on the same network.",
"sshDaemonDisclaimer": "Ensure your target host is properly configured to run the auth daemon before completing this setup, or provisioning will fail.",
"sshDaemonPort": "Daemon Port",
"sshServerDestination": "Server Destination",
"sshServerDestinationDescription": "Configure the destination and port of the SSH server",
"destination": "Destination",
"bgTargetMultiSiteDisclaimer": "Selecting multiple sites enables resilient routing and failover for high availability.",
"roleAllowSsh": "Permitir SSH",
"roleAllowSshAllow": "Autorizar",
"roleAllowSshDisallow": "Anular",
@@ -1957,7 +2087,7 @@
"sshSudoModeCommandsDescription": "Usuário só pode executar os comandos especificados com sudo.",
"sshSudo": "Permitir sudo",
"sshSudoCommands": "Comandos Sudo",
"sshSudoCommandsDescription": "Lista separada por vírgulas de comandos que o usuário pode executar com sudo.",
"sshSudoCommandsDescription": "Comma separated list of commands the user is allowed to run with sudo. Absolute paths must be used.",
"sshCreateHomeDir": "Criar Diretório Inicial",
"sshUnixGroups": "Grupos Unix",
"sshUnixGroupsDescription": "Grupos Unix separados por vírgulas para adicionar o usuário no host alvo.",
@@ -2049,8 +2179,9 @@
"editInternalResourceDialogModeCidr": "CIDR",
"editInternalResourceDialogModeHttp": "HTTP",
"editInternalResourceDialogModeHttps": "HTTPS",
"editInternalResourceDialogModeSsh": "SSH",
"editInternalResourceDialogScheme": "Esquema",
"editInternalResourceDialogEnableSsl": "Ativar TLS",
"editInternalResourceDialogEnableSsl": "Enable TLS",
"editInternalResourceDialogEnableSslDescription": "Ativar criptografia SSL/TLS para conexões HTTPS seguras com o destino.",
"editInternalResourceDialogDestination": "Destino",
"editInternalResourceDialogDestinationHostDescription": "O endereço IP ou o nome do host do recurso na rede do site.",
@@ -2098,9 +2229,10 @@
"createInternalResourceDialogModeCidr": "CIDR",
"createInternalResourceDialogModeHttp": "HTTP",
"createInternalResourceDialogModeHttps": "HTTPS",
"createInternalResourceDialogModeSsh": "SSH",
"scheme": "Esquema",
"createInternalResourceDialogScheme": "Esquema",
"createInternalResourceDialogEnableSsl": "Ativar TLS",
"createInternalResourceDialogEnableSsl": "Enable TLS",
"createInternalResourceDialogEnableSslDescription": "Ativar criptografia SSL/TLS para conexões HTTPS seguras com o destino.",
"createInternalResourceDialogDestination": "Destino",
"createInternalResourceDialogDestinationHostDescription": "O endereço IP ou o nome do host do recurso na rede do site.",
@@ -2233,7 +2365,7 @@
"description": "Servidor Pangolin auto-hospedado mais confiável e com baixa manutenção com sinos extras e assobiamentos",
"introTitle": "Pangolin Auto-Hospedado Gerenciado",
"introDescription": "é uma opção de implantação projetada para pessoas que querem simplicidade e confiança adicional, mantendo os seus dados privados e auto-hospedados.",
"introDetail": "Com esta opção, você ainda roda seu próprio nó Pangolin - seus túneis, terminação TLS e tráfego todos permanecem no seu servidor. A diferença é que a gestão e a monitorização são geridos através do nosso painel de nuvem, que desbloqueia vários benefícios:",
"introDetail": "With this option, you still run your own Pangolin node - your tunnels, TLS termination, and traffic all stay on your server. The difference is that management and monitoring are handled through our cloud dashboard, which unlocks a number of benefits:",
"benefitSimplerOperations": {
"title": "Operações simples",
"description": "Não é necessário executar o seu próprio servidor de e-mail ou configurar um alerta complexo. Você receberá fora de caixa verificações de saúde e alertas de tempo de inatividade."
@@ -2937,7 +3069,7 @@
"learnMore": "Saiba mais",
"backToHome": "Voltar para a página inicial",
"needToSignInToOrg": "Precisa usar o provedor de identidade da sua organização?",
"maintenanceMode": "Modo de Manutenção",
"maintenanceMode": "Maintenance Page",
"maintenanceModeDescription": "Exibir uma página de manutenção para os visitantes",
"maintenanceModeType": "Tipo de Modo de Manutenção",
"showMaintenancePage": "Mostrar uma página de manutenção para os visitantes",
@@ -2967,6 +3099,7 @@
"maintenanceScreenEstimatedCompletion": "Conclusão Estimada:",
"createInternalResourceDialogDestinationRequired": "Destino é obrigatório",
"available": "Disponível",
"disabledResourceDescription": "When disabled, the resource will be inaccessible by everyone.",
"archived": "Arquivado",
"noArchivedDevices": "Nenhum dispositivo arquivado encontrado",
"deviceArchived": "Dispositivo arquivado",
@@ -3296,5 +3429,6 @@
"memberPortalResourceDisabled": "Recurso Desativado",
"memberPortalShowingResources": "Mostrando {start}-{end} de {total} recursos",
"memberPortalPrevious": "Anterior",
"memberPortalNext": "Próximo"
"memberPortalNext": "Próximo",
"httpSettings": "HTTP Settings"
}

View File

@@ -176,6 +176,7 @@
"shareErrorCreateDescription": "Произошла ошибка при создании общей ссылки",
"shareCreateDescription": "Любой, у кого есть эта ссылка, может получить доступ к ресурсу",
"shareTitleOptional": "Заголовок (необязательно)",
"sharePathOptional": "Path (optional)",
"expireIn": "Срок действия",
"neverExpire": "Бессрочный доступ",
"shareExpireDescription": "Срок действия - это период, в течение которого ссылка будет работать и предоставлять доступ к ресурсу. После этого времени ссылка перестанет работать, и пользователи, использовавшие эту ссылку, потеряют доступ к ресурсу.",
@@ -208,11 +209,33 @@
"resourcesSearch": "Поиск ресурсов...",
"resourceAdd": "Добавить ресурс",
"resourceErrorDelte": "Ошибка при удалении ресурса",
"resourcePoliciesTitle": "Manage Resource Policies",
"resourcePoliciesAttachedResourcesColumnTitle": "Attached resources",
"resourcePoliciesAttachedResources": "{count} resource(s)",
"resourcePoliciesAttachedResourcesEmpty": "no resources",
"resourcePoliciesDescription": "Create and manage authentication policies to control access to your resources",
"resourcePoliciesSearch": "Search policies...",
"resourcePoliciesAdd": "Add Policy",
"resourcePoliciesDefaultBadgeText": "Default policy",
"resourcePoliciesCreate": "Create Resource Policy",
"resourcePoliciesCreateDescription": "Follow the steps below to create a new policy",
"resourcePolicyName": "Policy Name",
"resourcePolicyNameDescription": "Give this policy a name to identify it across your resources",
"resourcePolicyNamePlaceholder": "e.g. Internal Access Policy",
"resourcePoliciesSeeAll": "See All Policies",
"resourcePolicyAuthMethodAdd": "Add Authentication Method",
"resourcePolicyOtpEmailAdd": "Add OTP emails",
"resourcePolicyRulesAdd": "Add Rules",
"resourcePolicyAuthMethodsDescription": "Allow access to resources via additional auth methods",
"resourcePolicyUsersRolesDescription": "Configure which users and roles can visit associated resources",
"rulesResourcePolicyDescription": "Configure rules to control access resources associated to this policy",
"authentication": "Аутентификация",
"protected": "Защищён",
"notProtected": "Не защищён",
"resourceMessageRemove": "После удаления ресурс больше не будет доступен. Все целевые узлы, связанные с ресурсом, также будут удалены.",
"resourceQuestionRemove": "Вы уверены, что хотите удалить ресурс из организации?",
"resourcePolicyMessageRemove": "Once removed, the resource policy will no longer be accessible. All resources associated with the resource will be unlinked and left without authentication.",
"resourcePolicyQuestionRemove": "Are you sure you want to remove the resource policy from the organization?",
"resourceHTTP": "HTTPS-ресурс",
"resourceHTTPDescription": "Проксировать запросы через HTTPS с использованием полного доменного имени.",
"resourceRaw": "Сырой TCP/UDP-ресурс",
@@ -220,8 +243,9 @@
"resourceRawDescriptionCloud": "Прокси запросы через необработанный TCP/UDP с использованием номера порта. Требуется подключение сайтов к удаленному узлу.",
"resourceCreate": "Создание ресурса",
"resourceCreateDescription": "Следуйте инструкциям ниже для создания нового ресурса",
"resourceCreateGeneralDescription": "Configure the basic resource settings including the name and the type",
"resourceSeeAll": "Посмотреть все ресурсы",
"resourceInfo": "Информация о ресурсе",
"resourceCreateGeneral": "General",
"resourceNameDescription": "Отображаемое имя ресурса.",
"siteSelect": "Выберите сайт",
"siteSearch": "Поиск сайта",
@@ -231,12 +255,15 @@
"noCountryFound": "Страна не найдена.",
"siteSelectionDescription": "Этот сайт предоставит подключение к цели.",
"resourceType": "Тип ресурса",
"resourceTypeDescription": "Определить как получить доступ к ресурсу",
"resourceTypeDescription": "This controls the resource protocol and how it will be rendered in the browser. This cant be changed later.",
"resourceDomainDescription": "The resource will be served at this fully qualified domain name.",
"resourceHTTPSSettings": "Настройки HTTPS",
"resourceHTTPSSettingsDescription": "Настройка доступа к ресурсу по HTTPS",
"resourcePortDescription": "The external port on the Pangolin instance or node where the resource will be accessible.",
"domainType": "Тип домена",
"subdomain": "Поддомен",
"baseDomain": "Базовый домен",
"configure": "Configure",
"subdomnainDescription": "Поддомен, в котором ресурс будет доступен.",
"resourceRawSettings": "Настройки TCP/UDP",
"resourceRawSettingsDescription": "Настройка доступа к ресурсу по TCP/UDP",
@@ -253,8 +280,27 @@
"resourceLearnRaw": "Узнайте, как настроить TCP/UDP-ресурсы",
"resourceBack": "Назад к ресурсам",
"resourceGoTo": "Перейти к ресурсу",
"resourcePolicyDelete": "Delete Resource Policy",
"resourcePolicyDeleteConfirm": "Confirm Delete Resource Policy",
"resourceDelete": "Удалить ресурс",
"resourceDeleteConfirm": "Подтвердить удаление",
"labelDelete": "Delete Label",
"labelAdd": "Add Label",
"labelCreateSuccessMessage": "Label Created Successfully",
"labelEditSuccessMessage": "Label Modified Successfully",
"labelNameField": "Label Name",
"labelColorField": "Label Color",
"labelPlaceholder": "Ex: homelab",
"labelCreate": "Create Label",
"createLabelDialogTitle": "Create Label",
"createLabelDialogDescription": "Create a new label that can be attached to this organization",
"labelEdit": "Edit Label",
"editLabelDialogTitle": "Update Label",
"editLabelDialogDescription": "Edit a new label that can be attached to this organization",
"labelDeleteConfirm": "Confirm Delete Label",
"labelErrorDelete": "Failed to delete label",
"labelMessageRemove": "This action is permanent. All sites, resources, and clients tagged with this label will be untagged.",
"labelQuestionRemove": "Are you sure you want to remove the label from the organization?",
"visibility": "Видимость",
"enabled": "Включено",
"disabled": "Отключено",
@@ -265,6 +311,8 @@
"rules": "Правила",
"resourceSettingDescription": "Настройка параметров ресурса",
"resourceSetting": "Настройки {resourceName}",
"resourcePolicySettingDescription": "Configure the settings on the resource policy",
"resourcePolicySetting": "{policyName} Settings",
"alwaysAllow": "Авторизация байпасса",
"alwaysDeny": "Блокировать доступ",
"passToAuth": "Переход к аутентификации",
@@ -630,7 +678,7 @@
"createdAt": "Создано в",
"proxyErrorInvalidHeader": "Неверное значение пользовательского заголовка Host. Используйте формат доменного имени или оставьте пустым для сброса пользовательского заголовка Host.",
"proxyErrorTls": "Неверное имя TLS сервера. Используйте формат доменного имени или оставьте пустым для удаления имени TLS сервера.",
"proxyEnableSSL": "Включить TLS",
"proxyEnableSSL": "Enable TLS",
"proxyEnableSSLDescription": "Включить шифрование SSL/TLS для безопасных HTTPS соединений с целями.",
"target": "Target",
"configureTarget": "Настроить адресаты",
@@ -747,6 +795,16 @@
"rulesNoOne": "Нет правил. Добавьте правило с помощью формы.",
"rulesOrder": "Правила оцениваются по приоритету в возрастающем порядке.",
"rulesSubmit": "Сохранить правила",
"policyErrorCreate": "Error creating policy",
"policyErrorCreateDescription": "An error occurred when creating the policy",
"policyErrorCreateMessageDescription": "An unexpected error occurred",
"policyErrorUpdate": "Error updating policy",
"policyErrorUpdateDescription": "An error occurred when updating the policy",
"policyErrorUpdateMessageDescription": "An unexpected error occurred",
"policyCreatedSuccess": "Resource policy succesfully created",
"policyUpdatedSuccess": "Resource policy succesfully updated",
"authMethodsSave": "Save auth methods",
"rulesSave": "Save Rules",
"resourceErrorCreate": "Ошибка при создании ресурса",
"resourceErrorCreateDescription": "Произошла ошибка при создании ресурса",
"resourceErrorCreateMessage": "Ошибка создания ресурса:",
@@ -810,6 +868,16 @@
"pincodeAdd": "Добавить PIN-код",
"pincodeRemove": "Удалить PIN-код",
"resourceAuthMethods": "Методы аутентификации",
"resourcePolicyAuthMethodsEmpty": "No authentication method",
"resourcePolicyOtpEmpty": "No one time password",
"resourcePolicyReadOnly": "This policy is Read only",
"resourcePolicyReadOnlyDescription": "This resource policy is shared accross multiple resources, you cannot edit it on this page.",
"resourcePolicyTypeSave": "Save Resource type",
"resourcePolicySelect": "Select resource policy",
"resourcePolicySelectError": "Select a resource policy",
"resourcePolicyNotFound": "Policy not found",
"resourcePolicySearch": "Search policies",
"resourcePolicyRulesEmpty": "No authentication rules",
"resourceAuthMethodsDescriptions": "Разрешить доступ к ресурсу через дополнительные методы аутентификации",
"resourceAuthSettingsSave": "Успешно сохранено",
"resourceAuthSettingsSaveDescription": "Настройки аутентификации сохранены",
@@ -845,6 +913,12 @@
"resourcePincodeSetupTitle": "Установить PIN-код",
"resourcePincodeSetupTitleDescription": "Установите PIN-код для защиты этого ресурса",
"resourceRoleDescription": "Администраторы всегда имеют доступ к этому ресурсу.",
"resourcePolicySelectTitle": "Resource Access Policy",
"resourcePolicySelectDescription": "Select the resource policy type for authentication",
"resourcePolicyInline": "Inline Resource Policy",
"resourcePolicyInlineDescription": "Access Policy scoped to only this resource",
"resourcePolicyShared": "Shared Resource Policy",
"resourcePolicySharedDescription": "Access Policy shared accross multiple resources",
"resourceUsersRoles": "Контроль доступа",
"resourceUsersRolesDescription": "Выберите пользователей и роли с доступом к этому ресурсу",
"resourceUsersRolesSubmit": "Сохранить контроль доступа",
@@ -1140,6 +1214,18 @@
"idpErrorConnectingTo": "Возникла проблема при подключении к {name}. Пожалуйста, свяжитесь с вашим администратором.",
"idpErrorNotFound": "IdP не найден",
"inviteInvalid": "Недействительное приглашение",
"labels": "Labels",
"orgLabelsDescription": "Manage labels in this organization.",
"addLabels": "Add labels",
"siteLabelsTab": "Labels",
"siteLabelsDescription": "Manage labels associated with this site.",
"labelsNotFound": "Labels not found",
"labelSearch": "Search labels",
"accessLabelFilterCount": "{count, plural, one {# label} other {# labels}}",
"labelOverflowCount": "+{count, plural, one {# label} other {# labels}}",
"accessLabelFilterClear": "Clear label filters",
"selectColor": "Select color",
"createNewLabel": "Create new org label \"{label}\"",
"inviteInvalidDescription": "Ссылка на приглашение недействительна.",
"inviteErrorWrongUser": "Приглашение не для этого пользователя",
"inviteErrorUserNotExists": "Пользователь не существует. Пожалуйста, сначала создайте учетную запись.",
@@ -1374,6 +1460,8 @@
"sidebarResources": "Ресурсы",
"sidebarProxyResources": "Публичный",
"sidebarClientResources": "Приватный",
"sidebarPolicies": "Policies",
"sidebarResourcePolicies": "Resources",
"sidebarAccessControl": "Контроль доступа",
"sidebarLogsAndAnalytics": "Журналы и аналитика",
"sidebarTeam": "Команда",
@@ -1557,7 +1645,8 @@
"standaloneHcFilterSiteIdFallback": "Сайт {id}",
"standaloneHcFilterResourceIdFallback": "Ресурс {id}",
"blueprints": "Чертежи",
"blueprintsDescription": "Применить декларирующие конфигурации и просмотреть предыдущие запуски",
"blueprintsLog": "Blueprints Log",
"blueprintsDescription": "View past blueprint applications and their results",
"blueprintAdd": "Добавить чертёж",
"blueprintGoBack": "Посмотреть все чертежи",
"blueprintCreate": "Создать чертёж",
@@ -1575,7 +1664,17 @@
"contents": "Содержание",
"parsedContents": "Переработанное содержимое (только для чтения)",
"enableDockerSocket": "Включить чертёж Docker",
"enableDockerSocketDescription": "Включить scraping ярлыка Docker Socket для ярлыков чертежей. Путь к сокету должен быть предоставлен в Newt.",
"enableDockerSocketDescription": "Enable Docker Socket label scraping for blueprint labels. Socket path must be provided to the site connector. Read about how this works in <docsLink>the documentation</docsLink>.",
"newtAutoUpdate": "Enable Site Auto-Update",
"newtAutoUpdateDescription": "When enabled, site connectors will automatically update to the latest version when a new release is available.",
"siteAutoUpdate": "Site Auto-Update",
"siteAutoUpdateLabel": "Enable Auto-Update",
"siteAutoUpdateDescription": "Control whether this site's connector automatically downloads the latest version.",
"siteAutoUpdateOrgDefault": "Organization default: {state}",
"siteAutoUpdateOverriding": "Overriding organization setting",
"siteAutoUpdateResetToOrg": "Reset to Organization Default",
"siteAutoUpdateEnabled": "enabled",
"siteAutoUpdateDisabled": "disabled",
"viewDockerContainers": "Просмотр контейнеров Docker",
"containersIn": "Контейнеры в {siteName}",
"selectContainerDescription": "Выберите любой контейнер для использования в качестве имени хоста для этой цели. Нажмите на порт, чтобы использовать порт.",
@@ -1620,6 +1719,7 @@
"certificateStatus": "Сертификат",
"certificateStatusAutoRefreshHint": "Статус обновляется автоматически.",
"loading": "Загрузка",
"loadingEllipsis": "Loading...",
"loadingAnalytics": "Загрузка аналитики",
"restart": "Перезагрузка",
"domains": "Домены",
@@ -1846,6 +1946,7 @@
"billingManageLicenseSubscription": "Управление подпиской на платные лицензионные ключи собственного хостинга",
"billingCurrentKeys": "Текущие ключи",
"billingModifyCurrentPlan": "Изменить текущий план",
"billingManageLicenseSubscriptionDescription": "Manage your subscription for paid self-hosted license keys and download invoices.",
"billingConfirmUpgrade": "Подтвердить обновление",
"billingConfirmDowngrade": "Подтверждение понижения",
"billingConfirmUpgradeDescription": "Вы собираетесь обновить тарифный план. Проверьте новые лимиты и цены ниже.",
@@ -1943,7 +2044,36 @@
"timeIsInSeconds": "Время указано в секундах",
"requireDeviceApproval": "Требовать подтверждения устройства",
"requireDeviceApprovalDescription": "Пользователям с этой ролью нужны новые устройства, одобренные администратором, прежде чем они смогут подключаться и получать доступ к ресурсам.",
"sshAccess": "SSH доступ",
"sshSettings": "SSH Settings",
"rdpSettings": "RDP Settings",
"vncSettings": "VNC Settings",
"sshServer": "SSH Server",
"rdpServer": "RDP Server",
"vncServer": "VNC Server",
"sshServerDescription": "Set up the authentication method, daemon location, and server destination",
"rdpServerDescription": "Configure the destination and port of the RDP server",
"vncServerDescription": "Configure the destination and port of the VNC server",
"sshServerMode": "Mode",
"sshServerModeStandard": "Standard SSH Server",
"sshServerModePangolin": "Pangolin SSH",
"sshServerModeStandardDescription": "Routes commands over network to an SSH server such as OpenSSH.",
"sshServerModeNative": "Native SSH Server",
"sshServerModeNativeDescription": "Executes commands directly on the host via the Site Connector. No network config required.",
"sshAuthenticationMethod": "Authentication Method",
"sshAuthMethodManual": "Manual Authentication",
"sshAuthMethodManualDescription": "Requires existing host credentials. Bypasses automatic provisioning.",
"sshAuthMethodAutomated": "Automated Provisioning",
"sshAuthMethodAutomatedDescription": "Automatically creates users, groups, and sudo permissions on host.",
"sshAuthDaemonLocation": "Auth Daemon Location",
"sshDaemonLocationSiteDescription": "Executes locally on the machine hosting the site connector.",
"sshDaemonLocationRemote": "On Remote Host",
"sshDaemonLocationRemoteDescription": "Executes on a separate target machine on the same network.",
"sshDaemonDisclaimer": "Ensure your target host is properly configured to run the auth daemon before completing this setup, or provisioning will fail.",
"sshDaemonPort": "Daemon Port",
"sshServerDestination": "Server Destination",
"sshServerDestinationDescription": "Configure the destination and port of the SSH server",
"destination": "Destination",
"bgTargetMultiSiteDisclaimer": "Selecting multiple sites enables resilient routing and failover for high availability.",
"roleAllowSsh": "Разрешить SSH",
"roleAllowSshAllow": "Разрешить",
"roleAllowSshDisallow": "Запретить",
@@ -1957,7 +2087,7 @@
"sshSudoModeCommandsDescription": "Пользователь может запускать только указанные команды с помощью sudo.",
"sshSudo": "Разрешить sudo",
"sshSudoCommands": "Sudo Команды",
"sshSudoCommandsDescription": "Список команд, разделенных запятыми, которые пользователю разрешено запускать с помощью sudo.",
"sshSudoCommandsDescription": "Comma separated list of commands the user is allowed to run with sudo. Absolute paths must be used.",
"sshCreateHomeDir": "Создать домашний каталог",
"sshUnixGroups": "Unix группы",
"sshUnixGroupsDescription": "Группы Unix через запятую, чтобы добавить пользователя на целевой хост.",
@@ -2049,8 +2179,9 @@
"editInternalResourceDialogModeCidr": "СИДР",
"editInternalResourceDialogModeHttp": "HTTP",
"editInternalResourceDialogModeHttps": "HTTPS",
"editInternalResourceDialogModeSsh": "SSH",
"editInternalResourceDialogScheme": "Схема",
"editInternalResourceDialogEnableSsl": "Включить TLS",
"editInternalResourceDialogEnableSsl": "Enable TLS",
"editInternalResourceDialogEnableSslDescription": "Включите шифрование SSL/TLS для защищенных HTTPS соединений с конечной точкой.",
"editInternalResourceDialogDestination": "Пункт назначения",
"editInternalResourceDialogDestinationHostDescription": "IP адрес или имя хоста ресурса в сети сайта.",
@@ -2098,9 +2229,10 @@
"createInternalResourceDialogModeCidr": "СИДР",
"createInternalResourceDialogModeHttp": "HTTP",
"createInternalResourceDialogModeHttps": "HTTPS",
"createInternalResourceDialogModeSsh": "SSH",
"scheme": "Схема",
"createInternalResourceDialogScheme": "Схема",
"createInternalResourceDialogEnableSsl": "Включить TLS",
"createInternalResourceDialogEnableSsl": "Enable TLS",
"createInternalResourceDialogEnableSslDescription": "Включите SSL/TLS шифрование для защищенных HTTPS соединений с конечной точкой.",
"createInternalResourceDialogDestination": "Пункт назначения",
"createInternalResourceDialogDestinationHostDescription": "IP адрес или имя хоста ресурса в сети сайта.",
@@ -2233,7 +2365,7 @@
"description": "Более надежный и низко обслуживаемый сервер Pangolin с дополнительными колокольнями и свистками",
"introTitle": "Управляемый Само-Хост Панголина",
"introDescription": "- это вариант развертывания, предназначенный для людей, которые хотят простоты и надёжности, сохраняя при этом свои данные конфиденциальными и самостоятельными.",
"introDetail": "С помощью этой опции вы по-прежнему используете узел Pangolin - туннели, TLS, и весь остающийся на вашем сервере. Разница заключается в том, что управление и мониторинг осуществляются через нашу панель инструментов из облака, которая открывает ряд преимуществ:",
"introDetail": "With this option, you still run your own Pangolin node - your tunnels, TLS termination, and traffic all stay on your server. The difference is that management and monitoring are handled through our cloud dashboard, which unlocks a number of benefits:",
"benefitSimplerOperations": {
"title": "Более простые операции",
"description": "Не нужно запускать свой собственный почтовый сервер или настроить комплексное оповещение. Вы будете получать проверки состояния здоровья и оповещения о неисправностях из коробки."
@@ -2937,7 +3069,7 @@
"learnMore": "Узнать больше",
"backToHome": "Вернуться домой",
"needToSignInToOrg": "Нужно использовать провайдера идентификаций вашей организации?",
"maintenanceMode": "Режим обслуживания",
"maintenanceMode": "Maintenance Page",
"maintenanceModeDescription": "Показать страницу обслуживания посетителям",
"maintenanceModeType": "Тип режима обслуживания",
"showMaintenancePage": "Показать страницу обслуживания посетителям",
@@ -2967,6 +3099,7 @@
"maintenanceScreenEstimatedCompletion": "Предполагаемое завершение:",
"createInternalResourceDialogDestinationRequired": "Укажите адрес назначения. Это может быть имя хоста или IP-адрес.",
"available": "Доступно",
"disabledResourceDescription": "When disabled, the resource will be inaccessible by everyone.",
"archived": "Архивировано",
"noArchivedDevices": "Архивные устройства не найдены",
"deviceArchived": "Устройство архивировано",
@@ -3296,5 +3429,6 @@
"memberPortalResourceDisabled": "Ресурс отключён",
"memberPortalShowingResources": "Показаны {start}-{end} из {total} ресурсов",
"memberPortalPrevious": "Предыдущий",
"memberPortalNext": "Следующий"
"memberPortalNext": "Следующий",
"httpSettings": "HTTP Settings"
}

View File

@@ -176,6 +176,7 @@
"shareErrorCreateDescription": "Paylaşım bağlantısı oluşturulurken bir hata oluştu",
"shareCreateDescription": "Bu bağlantıya sahip olan herkes kaynağa erişebilir",
"shareTitleOptional": "Başlık (isteğe bağlı)",
"sharePathOptional": "Path (optional)",
"expireIn": "Süresi Dolacak",
"neverExpire": "Hiçbir Zaman Sona Ermez",
"shareExpireDescription": "Son kullanma süresi, bağlantının kullanılabilir ve kaynağa erişim sağlayacak süresidir. Bu süreden sonra bağlantı çalışmayı durduracak ve bu bağlantıyı kullanan kullanıcılar kaynağa erişimini kaybedecektir.",
@@ -208,11 +209,33 @@
"resourcesSearch": "Kaynakları ara...",
"resourceAdd": "Kaynak Ekle",
"resourceErrorDelte": "Kaynak silinirken hata",
"resourcePoliciesTitle": "Manage Resource Policies",
"resourcePoliciesAttachedResourcesColumnTitle": "Attached resources",
"resourcePoliciesAttachedResources": "{count} resource(s)",
"resourcePoliciesAttachedResourcesEmpty": "no resources",
"resourcePoliciesDescription": "Create and manage authentication policies to control access to your resources",
"resourcePoliciesSearch": "Search policies...",
"resourcePoliciesAdd": "Add Policy",
"resourcePoliciesDefaultBadgeText": "Default policy",
"resourcePoliciesCreate": "Create Resource Policy",
"resourcePoliciesCreateDescription": "Follow the steps below to create a new policy",
"resourcePolicyName": "Policy Name",
"resourcePolicyNameDescription": "Give this policy a name to identify it across your resources",
"resourcePolicyNamePlaceholder": "e.g. Internal Access Policy",
"resourcePoliciesSeeAll": "See All Policies",
"resourcePolicyAuthMethodAdd": "Add Authentication Method",
"resourcePolicyOtpEmailAdd": "Add OTP emails",
"resourcePolicyRulesAdd": "Add Rules",
"resourcePolicyAuthMethodsDescription": "Allow access to resources via additional auth methods",
"resourcePolicyUsersRolesDescription": "Configure which users and roles can visit associated resources",
"rulesResourcePolicyDescription": "Configure rules to control access resources associated to this policy",
"authentication": "Kimlik Doğrulama",
"protected": "Korunan",
"notProtected": "Korunmayan",
"resourceMessageRemove": "Kaldırıldıktan sonra kaynak artık erişilebilir olmayacaktır. Kaynakla ilişkili tüm hedefler de kaldırılacaktır.",
"resourceQuestionRemove": "Kaynağı organizasyondan kaldırmak istediğinizden emin misiniz?",
"resourcePolicyMessageRemove": "Once removed, the resource policy will no longer be accessible. All resources associated with the resource will be unlinked and left without authentication.",
"resourcePolicyQuestionRemove": "Are you sure you want to remove the resource policy from the organization?",
"resourceHTTP": "HTTPS Kaynağı",
"resourceHTTPDescription": "Tam nitelikli bir etki alanı adı kullanarak HTTPS üzerinden proxy isteklerini yönlendirin.",
"resourceRaw": "Ham TCP/UDP Kaynağı",
@@ -220,8 +243,9 @@
"resourceRawDescriptionCloud": "Proxy isteklerini bir port numarası kullanarak ham TCP/UDP üzerinden yapın. Sitelerin uzak bir düğüme bağlanması gereklidir.",
"resourceCreate": "Kaynak Oluştur",
"resourceCreateDescription": "Yeni bir kaynak oluşturmak için aşağıdaki adımları izleyin",
"resourceCreateGeneralDescription": "Configure the basic resource settings including the name and the type",
"resourceSeeAll": "Tüm Kaynakları Gör",
"resourceInfo": "Kaynak Bilgilendirmesi",
"resourceCreateGeneral": "General",
"resourceNameDescription": "Bu, kaynak için görünen addır.",
"siteSelect": "Site seç",
"siteSearch": "Site ara",
@@ -231,12 +255,15 @@
"noCountryFound": "Ülke bulunamadı.",
"siteSelectionDescription": "Bu site hedefe bağlantı sağlayacaktır.",
"resourceType": "Kaynak Türü",
"resourceTypeDescription": "Kaynağınıza nasıl erişmek istediğinizi belirleyin",
"resourceTypeDescription": "This controls the resource protocol and how it will be rendered in the browser. This cant be changed later.",
"resourceDomainDescription": "The resource will be served at this fully qualified domain name.",
"resourceHTTPSSettings": "HTTPS Ayarları",
"resourceHTTPSSettingsDescription": "Kaynağınıza HTTPS üzerinden erişimin nasıl sağlanacağını yapılandırın",
"resourcePortDescription": "The external port on the Pangolin instance or node where the resource will be accessible.",
"domainType": "Alan Türü",
"subdomain": "Alt Alan Adı",
"baseDomain": "Temel Alan Adı",
"configure": "Configure",
"subdomnainDescription": "Kaynağınızın erişilebileceği alt alan adı.",
"resourceRawSettings": "TCP/UDP Ayarları",
"resourceRawSettingsDescription": "Kaynaklara TCP/UDP üzerinden nasıl erişileceğini yapılandırın",
@@ -253,8 +280,27 @@
"resourceLearnRaw": "TCP/UDP kaynaklarını nasıl yapılandıracağınızı öğrenin",
"resourceBack": "Kaynaklara Geri Dön",
"resourceGoTo": "Kaynağa Git",
"resourcePolicyDelete": "Delete Resource Policy",
"resourcePolicyDeleteConfirm": "Confirm Delete Resource Policy",
"resourceDelete": "Kaynağı Sil",
"resourceDeleteConfirm": "Kaynak Silmeyi Onayla",
"labelDelete": "Delete Label",
"labelAdd": "Add Label",
"labelCreateSuccessMessage": "Label Created Successfully",
"labelEditSuccessMessage": "Label Modified Successfully",
"labelNameField": "Label Name",
"labelColorField": "Label Color",
"labelPlaceholder": "Ex: homelab",
"labelCreate": "Create Label",
"createLabelDialogTitle": "Create Label",
"createLabelDialogDescription": "Create a new label that can be attached to this organization",
"labelEdit": "Edit Label",
"editLabelDialogTitle": "Update Label",
"editLabelDialogDescription": "Edit a new label that can be attached to this organization",
"labelDeleteConfirm": "Confirm Delete Label",
"labelErrorDelete": "Failed to delete label",
"labelMessageRemove": "This action is permanent. All sites, resources, and clients tagged with this label will be untagged.",
"labelQuestionRemove": "Are you sure you want to remove the label from the organization?",
"visibility": "Görünürlük",
"enabled": "Etkin",
"disabled": "Devre Dışı",
@@ -265,6 +311,8 @@
"rules": "Kurallar",
"resourceSettingDescription": "Kaynağınızdaki ayarları yapılandırın",
"resourceSetting": "{resourceName} Ayarları",
"resourcePolicySettingDescription": "Configure the settings on the resource policy",
"resourcePolicySetting": "{policyName} Settings",
"alwaysAllow": "Kimlik Doğrulamayı Atla",
"alwaysDeny": "Erişimi Engelle",
"passToAuth": "Kimlik Doğrulamasına Geç",
@@ -630,7 +678,7 @@
"createdAt": "Oluşturulma Tarihi",
"proxyErrorInvalidHeader": "Geçersiz özel Ana Bilgisayar Başlığı değeri. Alan adı formatını kullanın veya özel Ana Bilgisayar Başlığını ayarlamak için boş bırakın.",
"proxyErrorTls": "Geçersiz TLS Sunucu Adı. Alan adı formatını kullanın veya TLS Sunucu Adını kaldırmak için boş bırakılsın.",
"proxyEnableSSL": "TLS Etkinleştir",
"proxyEnableSSL": "Enable TLS",
"proxyEnableSSLDescription": "Hedeflere güvenli HTTPS bağlantıları için SSL/TLS şifrelemesini etkinleştirin.",
"target": "Hedef",
"configureTarget": "Hedefleri Yapılandır",
@@ -747,6 +795,16 @@
"rulesNoOne": "Kural yok. Formu kullanarak bir kural ekleyin.",
"rulesOrder": "Kurallar, artan öncelik sırasına göre değerlendirilir.",
"rulesSubmit": "Kuralları Kaydet",
"policyErrorCreate": "Error creating policy",
"policyErrorCreateDescription": "An error occurred when creating the policy",
"policyErrorCreateMessageDescription": "An unexpected error occurred",
"policyErrorUpdate": "Error updating policy",
"policyErrorUpdateDescription": "An error occurred when updating the policy",
"policyErrorUpdateMessageDescription": "An unexpected error occurred",
"policyCreatedSuccess": "Resource policy succesfully created",
"policyUpdatedSuccess": "Resource policy succesfully updated",
"authMethodsSave": "Save auth methods",
"rulesSave": "Save Rules",
"resourceErrorCreate": "Kaynak oluşturma hatası",
"resourceErrorCreateDescription": "Kaynak oluşturulurken bir hata oluştu",
"resourceErrorCreateMessage": "Kaynak oluşturma hatası:",
@@ -810,6 +868,16 @@
"pincodeAdd": "PIN Kodu Ekle",
"pincodeRemove": "PIN Kodu Kaldır",
"resourceAuthMethods": "Kimlik Doğrulama Yöntemleri",
"resourcePolicyAuthMethodsEmpty": "No authentication method",
"resourcePolicyOtpEmpty": "No one time password",
"resourcePolicyReadOnly": "This policy is Read only",
"resourcePolicyReadOnlyDescription": "This resource policy is shared accross multiple resources, you cannot edit it on this page.",
"resourcePolicyTypeSave": "Save Resource type",
"resourcePolicySelect": "Select resource policy",
"resourcePolicySelectError": "Select a resource policy",
"resourcePolicyNotFound": "Policy not found",
"resourcePolicySearch": "Search policies",
"resourcePolicyRulesEmpty": "No authentication rules",
"resourceAuthMethodsDescriptions": "Ek kimlik doğrulama yöntemleriyle kaynağa erişime izin verin",
"resourceAuthSettingsSave": "Başarıyla kaydedildi",
"resourceAuthSettingsSaveDescription": "Kimlik doğrulama ayarları kaydedildi",
@@ -845,6 +913,12 @@
"resourcePincodeSetupTitle": "Pincode Ayarla",
"resourcePincodeSetupTitleDescription": "Bu kaynağı korumak için bir pincode ayarlayın",
"resourceRoleDescription": "Yöneticiler her zaman bu kaynağa erişebilir.",
"resourcePolicySelectTitle": "Resource Access Policy",
"resourcePolicySelectDescription": "Select the resource policy type for authentication",
"resourcePolicyInline": "Inline Resource Policy",
"resourcePolicyInlineDescription": "Access Policy scoped to only this resource",
"resourcePolicyShared": "Shared Resource Policy",
"resourcePolicySharedDescription": "Access Policy shared accross multiple resources",
"resourceUsersRoles": "Erişim Kontrolleri",
"resourceUsersRolesDescription": "Bu kaynağı kimlerin ziyaret edebileceği kullanıcıları ve rolleri yapılandırın",
"resourceUsersRolesSubmit": "Erişim Kontrollerini Kaydet",
@@ -1140,6 +1214,18 @@
"idpErrorConnectingTo": "{name} ile bağlantı kurarken bir sorun meydana geldi. Lütfen yöneticiye danışın.",
"idpErrorNotFound": "IdP bulunamadı",
"inviteInvalid": "Geçersiz Davet",
"labels": "Labels",
"orgLabelsDescription": "Manage labels in this organization.",
"addLabels": "Add labels",
"siteLabelsTab": "Labels",
"siteLabelsDescription": "Manage labels associated with this site.",
"labelsNotFound": "Labels not found",
"labelSearch": "Search labels",
"accessLabelFilterCount": "{count, plural, one {# label} other {# labels}}",
"labelOverflowCount": "+{count, plural, one {# label} other {# labels}}",
"accessLabelFilterClear": "Clear label filters",
"selectColor": "Select color",
"createNewLabel": "Create new org label \"{label}\"",
"inviteInvalidDescription": "Davet bağlantısı geçersiz.",
"inviteErrorWrongUser": "Davet bu kullanıcı için değil",
"inviteErrorUserNotExists": "Kullanıcı mevcut değil. Lütfen önce bir hesap oluşturun.",
@@ -1374,6 +1460,8 @@
"sidebarResources": "Kaynaklar",
"sidebarProxyResources": "Herkese Açık",
"sidebarClientResources": "Özel",
"sidebarPolicies": "Policies",
"sidebarResourcePolicies": "Resources",
"sidebarAccessControl": "Erişim Kontrolü",
"sidebarLogsAndAnalytics": "Kayıtlar & Analitik",
"sidebarTeam": "Ekip",
@@ -1557,7 +1645,8 @@
"standaloneHcFilterSiteIdFallback": "Site {id}",
"standaloneHcFilterResourceIdFallback": "Kaynak {id}",
"blueprints": "Planlar",
"blueprintsDescription": "Deklaratif yapılandırmaları uygulayın ve önceki çalışmaları görüntüleyin",
"blueprintsLog": "Blueprints Log",
"blueprintsDescription": "View past blueprint applications and their results",
"blueprintAdd": "Plan Ekle",
"blueprintGoBack": "Tüm Planları Gör",
"blueprintCreate": "Plan Oluştur",
@@ -1575,7 +1664,17 @@
"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.",
"enableDockerSocketDescription": "Enable Docker Socket label scraping for blueprint labels. Socket path must be provided to the site connector. Read about how this works in <docsLink>the documentation</docsLink>.",
"newtAutoUpdate": "Enable Site Auto-Update",
"newtAutoUpdateDescription": "When enabled, site connectors will automatically update to the latest version when a new release is available.",
"siteAutoUpdate": "Site Auto-Update",
"siteAutoUpdateLabel": "Enable Auto-Update",
"siteAutoUpdateDescription": "Control whether this site's connector automatically downloads the latest version.",
"siteAutoUpdateOrgDefault": "Organization default: {state}",
"siteAutoUpdateOverriding": "Overriding organization setting",
"siteAutoUpdateResetToOrg": "Reset to Organization Default",
"siteAutoUpdateEnabled": "enabled",
"siteAutoUpdateDisabled": "disabled",
"viewDockerContainers": "Docker Konteynerlerini Görüntüle",
"containersIn": "{siteName} içindeki konteynerler",
"selectContainerDescription": "Bu hedef için bir ana bilgisayar adı olarak kullanmak üzere herhangi bir konteyner seçin. Bir bağlantı noktası kullanmak için bir bağlantı noktasına tıklayın.",
@@ -1620,6 +1719,7 @@
"certificateStatus": "Sertifika",
"certificateStatusAutoRefreshHint": "Durum otomatik olarak yenilenir.",
"loading": "Yükleniyor",
"loadingEllipsis": "Loading...",
"loadingAnalytics": "Analiz Yükleniyor",
"restart": "Yeniden Başlat",
"domains": "Alan Adları",
@@ -1846,6 +1946,7 @@
"billingManageLicenseSubscription": "Kendi barındırdığınız ücretli lisans anahtarları için aboneliğinizi yönetin",
"billingCurrentKeys": "Mevcut Anahtarlar",
"billingModifyCurrentPlan": "Mevcut Planı Düzenle",
"billingManageLicenseSubscriptionDescription": "Manage your subscription for paid self-hosted license keys and download invoices.",
"billingConfirmUpgrade": "Yükseltmeyi Onayla",
"billingConfirmDowngrade": "Düşürmeyi Onayla",
"billingConfirmUpgradeDescription": "Planınızı yükseltmek üzeresiniz. Yeni limitleri ve fiyatları aşağıda inceleyin.",
@@ -1943,7 +2044,36 @@
"timeIsInSeconds": "Zaman saniye cinsindendir",
"requireDeviceApproval": "Cihaz Onaylarını Gerektir",
"requireDeviceApprovalDescription": "Bu role sahip kullanıcıların yeni cihazlarının bağlanabilmesi ve kaynaklara erişebilmesi için bir yönetici tarafından onaylanması gerekiyor.",
"sshAccess": "SSH Erişimi",
"sshSettings": "SSH Settings",
"rdpSettings": "RDP Settings",
"vncSettings": "VNC Settings",
"sshServer": "SSH Server",
"rdpServer": "RDP Server",
"vncServer": "VNC Server",
"sshServerDescription": "Set up the authentication method, daemon location, and server destination",
"rdpServerDescription": "Configure the destination and port of the RDP server",
"vncServerDescription": "Configure the destination and port of the VNC server",
"sshServerMode": "Mode",
"sshServerModeStandard": "Standard SSH Server",
"sshServerModePangolin": "Pangolin SSH",
"sshServerModeStandardDescription": "Routes commands over network to an SSH server such as OpenSSH.",
"sshServerModeNative": "Native SSH Server",
"sshServerModeNativeDescription": "Executes commands directly on the host via the Site Connector. No network config required.",
"sshAuthenticationMethod": "Authentication Method",
"sshAuthMethodManual": "Manual Authentication",
"sshAuthMethodManualDescription": "Requires existing host credentials. Bypasses automatic provisioning.",
"sshAuthMethodAutomated": "Automated Provisioning",
"sshAuthMethodAutomatedDescription": "Automatically creates users, groups, and sudo permissions on host.",
"sshAuthDaemonLocation": "Auth Daemon Location",
"sshDaemonLocationSiteDescription": "Executes locally on the machine hosting the site connector.",
"sshDaemonLocationRemote": "On Remote Host",
"sshDaemonLocationRemoteDescription": "Executes on a separate target machine on the same network.",
"sshDaemonDisclaimer": "Ensure your target host is properly configured to run the auth daemon before completing this setup, or provisioning will fail.",
"sshDaemonPort": "Daemon Port",
"sshServerDestination": "Server Destination",
"sshServerDestinationDescription": "Configure the destination and port of the SSH server",
"destination": "Destination",
"bgTargetMultiSiteDisclaimer": "Selecting multiple sites enables resilient routing and failover for high availability.",
"roleAllowSsh": "SSH'a İzin Ver",
"roleAllowSshAllow": "İzin Ver",
"roleAllowSshDisallow": "İzin Verme",
@@ -1957,7 +2087,7 @@
"sshSudoModeCommandsDescription": "Kullanıcı sadece belirtilen komutları sudo ile çalıştırabilir.",
"sshSudo": "Sudo'ya izin ver",
"sshSudoCommands": "Sudo Komutları",
"sshSudoCommandsDescription": "Kullanıcının sudo ile çalıştırmasına izin verilen komutların virgülle ayrılmış listesi.",
"sshSudoCommandsDescription": "Comma separated list of commands the user is allowed to run with sudo. Absolute paths must be used.",
"sshCreateHomeDir": "Ev Dizini Oluştur",
"sshUnixGroups": "Unix Grupları",
"sshUnixGroupsDescription": "Hedef konakta kullanıcıya eklenecek Unix gruplarının virgülle ayrılmış listesi.",
@@ -2049,8 +2179,9 @@
"editInternalResourceDialogModeCidr": "CIDR",
"editInternalResourceDialogModeHttp": "HTTP",
"editInternalResourceDialogModeHttps": "HTTPS",
"editInternalResourceDialogModeSsh": "SSH",
"editInternalResourceDialogScheme": "Şema",
"editInternalResourceDialogEnableSsl": "TLS Etkinleştir",
"editInternalResourceDialogEnableSsl": "Enable TLS",
"editInternalResourceDialogEnableSslDescription": "Hedefe güvenli HTTPS bağlantıları için SSL/TLS şifrelemeyi etkinleştirin.",
"editInternalResourceDialogDestination": "Hedef",
"editInternalResourceDialogDestinationHostDescription": "Site ağındaki kaynağın IP adresi veya ana bilgisayar adı.",
@@ -2098,9 +2229,10 @@
"createInternalResourceDialogModeCidr": "CIDR",
"createInternalResourceDialogModeHttp": "HTTP",
"createInternalResourceDialogModeHttps": "HTTPS",
"createInternalResourceDialogModeSsh": "SSH",
"scheme": "Şema",
"createInternalResourceDialogScheme": "Şema",
"createInternalResourceDialogEnableSsl": "TLS'yi Etkinleştir",
"createInternalResourceDialogEnableSsl": "Enable TLS",
"createInternalResourceDialogEnableSslDescription": "Hedefe güvenli HTTPS bağlantıları için SSL/TLS şifrelemeyi etkinleştirin.",
"createInternalResourceDialogDestination": "Hedef",
"createInternalResourceDialogDestinationHostDescription": "Site ağındaki kaynağın IP adresi veya ana bilgisayar adı.",
@@ -2233,7 +2365,7 @@
"description": "Daha güvenilir ve düşük bakım gerektiren, ekstra özelliklere sahip kendi kendine barındırabileceğiniz Pangolin sunucusu",
"introTitle": "Yönetilen Kendi Kendine Barındırılan Pangolin",
"introDescription": "Bu, basitlik ve ekstra güvenilirlik arayan, ancak verilerini gizli tutmak ve kendi sunucularında barındırmak isteyen kişiler için tasarlanmış bir dağıtım seçeneğidir.",
"introDetail": "Bu seçenekle, kendi Pangolin düğümünüzü çalıştırmaya devam edersiniz - tünelleriniz, TLS bitişiniz ve trafiğiniz tamamen sunucunuzda kalır. Fark, yönetim ve izlemeyi bulut panomuz üzerinden gerçekleştiririz, bu da bir dizi avantaj sağlar:",
"introDetail": "With this option, you still run your own Pangolin node - your tunnels, TLS termination, and traffic all stay on your server. The difference is that management and monitoring are handled through our cloud dashboard, which unlocks a number of benefits:",
"benefitSimplerOperations": {
"title": "Daha basit işlemler",
"description": "Kendi e-posta sunucunuzu çalıştırmanıza veya karmaşık uyarılar kurmanıza gerek yok. Sağlık kontrolleri ve kesinti uyarılarını kutudan çıktığı gibi alırsınız."
@@ -2937,7 +3069,7 @@
"learnMore": "Daha fazla bilgi",
"backToHome": "Ana sayfaya geri dön",
"needToSignInToOrg": "Kuruluşunuzun kimlik sağlayıcısını kullanmanız mı gerekiyor?",
"maintenanceMode": "Bakım Modu",
"maintenanceMode": "Maintenance Page",
"maintenanceModeDescription": "Ziyaretçilere bir bakım sayfası gösterin",
"maintenanceModeType": "Bakım Modu Türü",
"showMaintenancePage": "Ziyaretçilere bir bakım sayfası gösterin",
@@ -2967,6 +3099,7 @@
"maintenanceScreenEstimatedCompletion": "Tahmini Tamamlama:",
"createInternalResourceDialogDestinationRequired": "Hedef gereklidir",
"available": "Mevcut",
"disabledResourceDescription": "When disabled, the resource will be inaccessible by everyone.",
"archived": "Arşivlenmiş",
"noArchivedDevices": "Arşivlenmiş cihaz bulunamadı",
"deviceArchived": "Cihaz arşivlendi",
@@ -3296,5 +3429,6 @@
"memberPortalResourceDisabled": "Kaynak Devre Dışı",
"memberPortalShowingResources": "{total} kaynaktan {start}-{end} gösteriliyor",
"memberPortalPrevious": "Önceki",
"memberPortalNext": "Sonraki"
"memberPortalNext": "Sonraki",
"httpSettings": "HTTP Settings"
}

View File

@@ -176,6 +176,7 @@
"shareErrorCreateDescription": "创建共享链接时出错",
"shareCreateDescription": "任何具有此链接的人都可以访问资源",
"shareTitleOptional": "标题 (可选)",
"sharePathOptional": "Path (optional)",
"expireIn": "过期时间",
"neverExpire": "永不过期",
"shareExpireDescription": "过期时间是链接可以使用并提供对资源的访问时间。 此时间后,链接将不再工作,使用此链接的用户将失去对资源的访问。",
@@ -208,11 +209,33 @@
"resourcesSearch": "搜索资源...",
"resourceAdd": "添加资源",
"resourceErrorDelte": "删除资源时出错",
"resourcePoliciesTitle": "Manage Resource Policies",
"resourcePoliciesAttachedResourcesColumnTitle": "Attached resources",
"resourcePoliciesAttachedResources": "{count} resource(s)",
"resourcePoliciesAttachedResourcesEmpty": "no resources",
"resourcePoliciesDescription": "Create and manage authentication policies to control access to your resources",
"resourcePoliciesSearch": "Search policies...",
"resourcePoliciesAdd": "Add Policy",
"resourcePoliciesDefaultBadgeText": "Default policy",
"resourcePoliciesCreate": "Create Resource Policy",
"resourcePoliciesCreateDescription": "Follow the steps below to create a new policy",
"resourcePolicyName": "Policy Name",
"resourcePolicyNameDescription": "Give this policy a name to identify it across your resources",
"resourcePolicyNamePlaceholder": "e.g. Internal Access Policy",
"resourcePoliciesSeeAll": "See All Policies",
"resourcePolicyAuthMethodAdd": "Add Authentication Method",
"resourcePolicyOtpEmailAdd": "Add OTP emails",
"resourcePolicyRulesAdd": "Add Rules",
"resourcePolicyAuthMethodsDescription": "Allow access to resources via additional auth methods",
"resourcePolicyUsersRolesDescription": "Configure which users and roles can visit associated resources",
"rulesResourcePolicyDescription": "Configure rules to control access resources associated to this policy",
"authentication": "认证",
"protected": "受到保护",
"notProtected": "未受到保护",
"resourceMessageRemove": "一旦删除,资源将不再可访问。与该资源相关的所有目标也将被删除。",
"resourceQuestionRemove": "您确定要从组织中删除资源吗?",
"resourcePolicyMessageRemove": "Once removed, the resource policy will no longer be accessible. All resources associated with the resource will be unlinked and left without authentication.",
"resourcePolicyQuestionRemove": "Are you sure you want to remove the resource policy from the organization?",
"resourceHTTP": "HTTPS 资源",
"resourceHTTPDescription": "通过使用完全限定的域名的HTTPS代理请求。",
"resourceRaw": "TCP/UDP 资源",
@@ -220,8 +243,9 @@
"resourceRawDescriptionCloud": "正在使用端口号使用 TCP/UDP 代理请求。需要站点连接到远程节点。",
"resourceCreate": "创建资源",
"resourceCreateDescription": "按照下面的步骤创建新资源",
"resourceCreateGeneralDescription": "Configure the basic resource settings including the name and the type",
"resourceSeeAll": "查看所有资源",
"resourceInfo": "资源信息",
"resourceCreateGeneral": "General",
"resourceNameDescription": "这是资源的显示名称。",
"siteSelect": "选择站点",
"siteSearch": "搜索站点",
@@ -231,12 +255,15 @@
"noCountryFound": "找不到国家。",
"siteSelectionDescription": "此站点将为目标提供连接。",
"resourceType": "资源类型",
"resourceTypeDescription": "确定如何访问资源",
"resourceTypeDescription": "This controls the resource protocol and how it will be rendered in the browser. This cant be changed later.",
"resourceDomainDescription": "The resource will be served at this fully qualified domain name.",
"resourceHTTPSSettings": "HTTPS 设置",
"resourceHTTPSSettingsDescription": "配置如何通过 HTTPS 访问资源",
"resourcePortDescription": "The external port on the Pangolin instance or node where the resource will be accessible.",
"domainType": "域类型",
"subdomain": "子域名",
"baseDomain": "根域名",
"configure": "Configure",
"subdomnainDescription": "可访问资源的子域。",
"resourceRawSettings": "TCP/UDP 设置",
"resourceRawSettingsDescription": "配置如何通过 TCP/UDP 访问资源",
@@ -253,8 +280,27 @@
"resourceLearnRaw": "学习如何配置 TCP/UDP 资源",
"resourceBack": "返回资源",
"resourceGoTo": "转到资源",
"resourcePolicyDelete": "Delete Resource Policy",
"resourcePolicyDeleteConfirm": "Confirm Delete Resource Policy",
"resourceDelete": "删除资源",
"resourceDeleteConfirm": "确认删除资源",
"labelDelete": "Delete Label",
"labelAdd": "Add Label",
"labelCreateSuccessMessage": "Label Created Successfully",
"labelEditSuccessMessage": "Label Modified Successfully",
"labelNameField": "Label Name",
"labelColorField": "Label Color",
"labelPlaceholder": "Ex: homelab",
"labelCreate": "Create Label",
"createLabelDialogTitle": "Create Label",
"createLabelDialogDescription": "Create a new label that can be attached to this organization",
"labelEdit": "Edit Label",
"editLabelDialogTitle": "Update Label",
"editLabelDialogDescription": "Edit a new label that can be attached to this organization",
"labelDeleteConfirm": "Confirm Delete Label",
"labelErrorDelete": "Failed to delete label",
"labelMessageRemove": "This action is permanent. All sites, resources, and clients tagged with this label will be untagged.",
"labelQuestionRemove": "Are you sure you want to remove the label from the organization?",
"visibility": "可见性",
"enabled": "已启用",
"disabled": "已禁用",
@@ -265,6 +311,8 @@
"rules": "规则",
"resourceSettingDescription": "配置资源上的设置",
"resourceSetting": "{resourceName} 设置",
"resourcePolicySettingDescription": "Configure the settings on the resource policy",
"resourcePolicySetting": "{policyName} Settings",
"alwaysAllow": "旁路认证",
"alwaysDeny": "屏蔽访问",
"passToAuth": "传递至认证",
@@ -630,7 +678,7 @@
"createdAt": "创建于",
"proxyErrorInvalidHeader": "无效的自定义主机头值。使用域名格式,或将空保存为取消自定义主机头。",
"proxyErrorTls": "无效的 TLS 服务器名称。使用域名格式,或保存空以删除 TLS 服务器名称。",
"proxyEnableSSL": "启用 TLS",
"proxyEnableSSL": "Enable TLS",
"proxyEnableSSLDescription": "启用 SSL/TLS 加密以确保目标的 HTTPS 连接。",
"target": "Target",
"configureTarget": "配置目标",
@@ -747,6 +795,16 @@
"rulesNoOne": "没有规则。使用表单添加规则。",
"rulesOrder": "规则按优先顺序评定。",
"rulesSubmit": "保存规则",
"policyErrorCreate": "Error creating policy",
"policyErrorCreateDescription": "An error occurred when creating the policy",
"policyErrorCreateMessageDescription": "An unexpected error occurred",
"policyErrorUpdate": "Error updating policy",
"policyErrorUpdateDescription": "An error occurred when updating the policy",
"policyErrorUpdateMessageDescription": "An unexpected error occurred",
"policyCreatedSuccess": "Resource policy succesfully created",
"policyUpdatedSuccess": "Resource policy succesfully updated",
"authMethodsSave": "Save auth methods",
"rulesSave": "Save Rules",
"resourceErrorCreate": "创建资源时出错",
"resourceErrorCreateDescription": "创建资源时出错",
"resourceErrorCreateMessage": "创建资源时发生错误:",
@@ -810,6 +868,16 @@
"pincodeAdd": "添加 PIN 码",
"pincodeRemove": "移除 PIN 码",
"resourceAuthMethods": "身份验证方法",
"resourcePolicyAuthMethodsEmpty": "No authentication method",
"resourcePolicyOtpEmpty": "No one time password",
"resourcePolicyReadOnly": "This policy is Read only",
"resourcePolicyReadOnlyDescription": "This resource policy is shared accross multiple resources, you cannot edit it on this page.",
"resourcePolicyTypeSave": "Save Resource type",
"resourcePolicySelect": "Select resource policy",
"resourcePolicySelectError": "Select a resource policy",
"resourcePolicyNotFound": "Policy not found",
"resourcePolicySearch": "Search policies",
"resourcePolicyRulesEmpty": "No authentication rules",
"resourceAuthMethodsDescriptions": "允许通过额外的认证方法访问资源",
"resourceAuthSettingsSave": "保存成功",
"resourceAuthSettingsSaveDescription": "已保存身份验证设置",
@@ -845,6 +913,12 @@
"resourcePincodeSetupTitle": "设置 PIN 码",
"resourcePincodeSetupTitleDescription": "设置 PIN 码来保护此资源",
"resourceRoleDescription": "管理员总是可以访问此资源。",
"resourcePolicySelectTitle": "Resource Access Policy",
"resourcePolicySelectDescription": "Select the resource policy type for authentication",
"resourcePolicyInline": "Inline Resource Policy",
"resourcePolicyInlineDescription": "Access Policy scoped to only this resource",
"resourcePolicyShared": "Shared Resource Policy",
"resourcePolicySharedDescription": "Access Policy shared accross multiple resources",
"resourceUsersRoles": "访问控制",
"resourceUsersRolesDescription": "配置用户和角色可以访问此资源",
"resourceUsersRolesSubmit": "保存访问控制",
@@ -1140,6 +1214,18 @@
"idpErrorConnectingTo": "无法连接到 {name},请联系管理员协助处理。",
"idpErrorNotFound": "找不到 IdP",
"inviteInvalid": "无效邀请",
"labels": "Labels",
"orgLabelsDescription": "Manage labels in this organization.",
"addLabels": "Add labels",
"siteLabelsTab": "Labels",
"siteLabelsDescription": "Manage labels associated with this site.",
"labelsNotFound": "Labels not found",
"labelSearch": "Search labels",
"accessLabelFilterCount": "{count, plural, one {# label} other {# labels}}",
"labelOverflowCount": "+{count, plural, one {# label} other {# labels}}",
"accessLabelFilterClear": "Clear label filters",
"selectColor": "Select color",
"createNewLabel": "Create new org label \"{label}\"",
"inviteInvalidDescription": "邀请链接无效。",
"inviteErrorWrongUser": "邀请不是该用户的",
"inviteErrorUserNotExists": "用户不存在。请先创建帐户。",
@@ -1374,6 +1460,8 @@
"sidebarResources": "资源",
"sidebarProxyResources": "公开的",
"sidebarClientResources": "非公开的",
"sidebarPolicies": "Policies",
"sidebarResourcePolicies": "Resources",
"sidebarAccessControl": "访问控制",
"sidebarLogsAndAnalytics": "日志与分析",
"sidebarTeam": "团队",
@@ -1557,7 +1645,8 @@
"standaloneHcFilterSiteIdFallback": "站点 {id}",
"standaloneHcFilterResourceIdFallback": "资源 {id}",
"blueprints": "蓝图",
"blueprintsDescription": "应用声明配置并查看先前运行的",
"blueprintsLog": "Blueprints Log",
"blueprintsDescription": "View past blueprint applications and their results",
"blueprintAdd": "添加蓝图",
"blueprintGoBack": "查看所有蓝图",
"blueprintCreate": "创建蓝图",
@@ -1575,7 +1664,17 @@
"contents": "目录",
"parsedContents": "解析内容 (只读)",
"enableDockerSocket": "启用 Docker 蓝图",
"enableDockerSocketDescription": "启用 Docker Socket 标签擦除蓝图标签。套接字路径必须提供给新的。",
"enableDockerSocketDescription": "Enable Docker Socket label scraping for blueprint labels. Socket path must be provided to the site connector. Read about how this works in <docsLink>the documentation</docsLink>.",
"newtAutoUpdate": "Enable Site Auto-Update",
"newtAutoUpdateDescription": "When enabled, site connectors will automatically update to the latest version when a new release is available.",
"siteAutoUpdate": "Site Auto-Update",
"siteAutoUpdateLabel": "Enable Auto-Update",
"siteAutoUpdateDescription": "Control whether this site's connector automatically downloads the latest version.",
"siteAutoUpdateOrgDefault": "Organization default: {state}",
"siteAutoUpdateOverriding": "Overriding organization setting",
"siteAutoUpdateResetToOrg": "Reset to Organization Default",
"siteAutoUpdateEnabled": "enabled",
"siteAutoUpdateDisabled": "disabled",
"viewDockerContainers": "查看停靠容器",
"containersIn": "{siteName} 中的容器",
"selectContainerDescription": "选择任何容器作为目标的主机名。点击端口使用端口。",
@@ -1620,6 +1719,7 @@
"certificateStatus": "证书",
"certificateStatusAutoRefreshHint": "状态自动刷新。",
"loading": "加载中",
"loadingEllipsis": "Loading...",
"loadingAnalytics": "加载分析",
"restart": "重启",
"domains": "域",
@@ -1846,6 +1946,7 @@
"billingManageLicenseSubscription": "管理您对付费的自托管许可证密钥的订阅",
"billingCurrentKeys": "当前密钥",
"billingModifyCurrentPlan": "修改当前计划",
"billingManageLicenseSubscriptionDescription": "Manage your subscription for paid self-hosted license keys and download invoices.",
"billingConfirmUpgrade": "确认升级",
"billingConfirmDowngrade": "确认降级",
"billingConfirmUpgradeDescription": "您即将升级您的计划。请检查下面的新限额和定价。",
@@ -1943,7 +2044,36 @@
"timeIsInSeconds": "时间以秒为单位",
"requireDeviceApproval": "需要设备批准",
"requireDeviceApprovalDescription": "具有此角色的用户需要管理员批准的新设备才能连接和访问资源。",
"sshAccess": "SSH 访问",
"sshSettings": "SSH Settings",
"rdpSettings": "RDP Settings",
"vncSettings": "VNC Settings",
"sshServer": "SSH Server",
"rdpServer": "RDP Server",
"vncServer": "VNC Server",
"sshServerDescription": "Set up the authentication method, daemon location, and server destination",
"rdpServerDescription": "Configure the destination and port of the RDP server",
"vncServerDescription": "Configure the destination and port of the VNC server",
"sshServerMode": "Mode",
"sshServerModeStandard": "Standard SSH Server",
"sshServerModePangolin": "Pangolin SSH",
"sshServerModeStandardDescription": "Routes commands over network to an SSH server such as OpenSSH.",
"sshServerModeNative": "Native SSH Server",
"sshServerModeNativeDescription": "Executes commands directly on the host via the Site Connector. No network config required.",
"sshAuthenticationMethod": "Authentication Method",
"sshAuthMethodManual": "Manual Authentication",
"sshAuthMethodManualDescription": "Requires existing host credentials. Bypasses automatic provisioning.",
"sshAuthMethodAutomated": "Automated Provisioning",
"sshAuthMethodAutomatedDescription": "Automatically creates users, groups, and sudo permissions on host.",
"sshAuthDaemonLocation": "Auth Daemon Location",
"sshDaemonLocationSiteDescription": "Executes locally on the machine hosting the site connector.",
"sshDaemonLocationRemote": "On Remote Host",
"sshDaemonLocationRemoteDescription": "Executes on a separate target machine on the same network.",
"sshDaemonDisclaimer": "Ensure your target host is properly configured to run the auth daemon before completing this setup, or provisioning will fail.",
"sshDaemonPort": "Daemon Port",
"sshServerDestination": "Server Destination",
"sshServerDestinationDescription": "Configure the destination and port of the SSH server",
"destination": "Destination",
"bgTargetMultiSiteDisclaimer": "Selecting multiple sites enables resilient routing and failover for high availability.",
"roleAllowSsh": "允许 SSH",
"roleAllowSshAllow": "允许",
"roleAllowSshDisallow": "不允许",
@@ -1957,7 +2087,7 @@
"sshSudoModeCommandsDescription": "用户只能用 sudo 运行指定的命令。",
"sshSudo": "允许Sudo",
"sshSudoCommands": "Sudo 命令",
"sshSudoCommandsDescription": "逗号分隔的用户允许使用 sudo 运行的命令列表。",
"sshSudoCommandsDescription": "Comma separated list of commands the user is allowed to run with sudo. Absolute paths must be used.",
"sshCreateHomeDir": "创建主目录",
"sshUnixGroups": "Unix 组",
"sshUnixGroupsDescription": "用逗号分隔了Unix组将用户添加到目标主机上。",
@@ -2049,8 +2179,9 @@
"editInternalResourceDialogModeCidr": "CIDR",
"editInternalResourceDialogModeHttp": "HTTP",
"editInternalResourceDialogModeHttps": "HTTPS",
"editInternalResourceDialogModeSsh": "SSH",
"editInternalResourceDialogScheme": "方案",
"editInternalResourceDialogEnableSsl": "启用 TLS",
"editInternalResourceDialogEnableSsl": "Enable TLS",
"editInternalResourceDialogEnableSslDescription": "为目标的安全 HTTPS 连接启用 SSL/TLS 加密。",
"editInternalResourceDialogDestination": "目标",
"editInternalResourceDialogDestinationHostDescription": "站点网络上资源的 IP 地址或主机名。",
@@ -2098,9 +2229,10 @@
"createInternalResourceDialogModeCidr": "CIDR",
"createInternalResourceDialogModeHttp": "HTTP",
"createInternalResourceDialogModeHttps": "HTTPS",
"createInternalResourceDialogModeSsh": "SSH",
"scheme": "方案",
"createInternalResourceDialogScheme": "方案",
"createInternalResourceDialogEnableSsl": "启用 TLS",
"createInternalResourceDialogEnableSsl": "Enable TLS",
"createInternalResourceDialogEnableSslDescription": "为目标的安全 HTTPS 连接启用 SSL/TLS 加密。",
"createInternalResourceDialogDestination": "目标",
"createInternalResourceDialogDestinationHostDescription": "站点网络上资源的 IP 地址或主机名。",
@@ -2233,7 +2365,7 @@
"description": "更可靠和低维护自我托管的 Pangolin 服务器,带有额外的铃声和告密器",
"introTitle": "托管自托管的潘戈林公司",
"introDescription": "这是一种部署选择,为那些希望简洁和额外可靠的人设计,同时仍然保持他们的数据的私密性和自我托管性。",
"introDetail": "通过此选项,您仍然运行您自己的 Pangolin 节点 - - 您的隧道、TLS 终止,并且流量在您的服务器上保持所有状态。 不同之处在于,管理和监测是通过我们的云层仪表板进行的,该仪表板开启了一些好处:",
"introDetail": "With this option, you still run your own Pangolin node - your tunnels, TLS termination, and traffic all stay on your server. The difference is that management and monitoring are handled through our cloud dashboard, which unlocks a number of benefits:",
"benefitSimplerOperations": {
"title": "简单的操作",
"description": "无需运行您自己的邮件服务器或设置复杂的警报。您将从方框中获得健康检查和下限提醒。"
@@ -2937,7 +3069,7 @@
"learnMore": "了解更多",
"backToHome": "返回首页",
"needToSignInToOrg": "需要使用您组织的身份提供商吗?",
"maintenanceMode": "维护模式",
"maintenanceMode": "Maintenance Page",
"maintenanceModeDescription": "向访客显示维护页面",
"maintenanceModeType": "维护模式类型",
"showMaintenancePage": "只在所有后端目标都故障或不健康时显示维护页面。只要至少一个目标健康,您的资源将正常工作。",
@@ -2967,6 +3099,7 @@
"maintenanceScreenEstimatedCompletion": "预计完成时间:",
"createInternalResourceDialogDestinationRequired": "需要目标地址",
"available": "可用",
"disabledResourceDescription": "When disabled, the resource will be inaccessible by everyone.",
"archived": "已存档",
"noArchivedDevices": "未找到存档设备",
"deviceArchived": "设备已存档",
@@ -3296,5 +3429,6 @@
"memberPortalResourceDisabled": "资源已禁用",
"memberPortalShowingResources": "显示 {start}-{end} 共 {total} 个资源",
"memberPortalPrevious": "上一页",
"memberPortalNext": "下一页"
"memberPortalNext": "下一页",
"httpSettings": "HTTP Settings"
}

View File

@@ -5,12 +5,8 @@ const withNextIntl = createNextIntlPlugin();
const nextConfig: NextConfig = {
reactStrictMode: false,
eslint: {
ignoreDuringBuilds: true
},
experimental: {
reactCompiler: true
},
reactCompiler: true,
transpilePackages: ["@novnc/novnc"],
output: "standalone"
};

5890
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -32,13 +32,15 @@
"format": "prettier --write ."
},
"dependencies": {
"@asteasolutions/zod-to-openapi": "8.4.1",
"@aws-sdk/client-s3": "3.1011.0",
"@faker-js/faker": "10.3.0",
"@headlessui/react": "2.2.9",
"@asteasolutions/zod-to-openapi": "8.5.0",
"@aws-sdk/client-s3": "3.1047.0",
"@headlessui/react": "2.2.10",
"@devolutions/iron-remote-desktop": "https://static.pangolin.net/packages/devolutions-iron-remote-desktop-0.0.0.tgz",
"@devolutions/iron-remote-desktop-rdp": "https://static.pangolin.net/packages/devolutions-iron-remote-desktop-rdp-0.0.0.tgz",
"@hookform/resolvers": "5.2.2",
"@monaco-editor/react": "4.7.0",
"@node-rs/argon2": "2.0.2",
"@novnc/novnc": "^1.7.0",
"@oslojs/crypto": "1.0.1",
"@oslojs/encoding": "1.1.0",
"@radix-ui/react-avatar": "1.1.11",
@@ -59,16 +61,20 @@
"@radix-ui/react-tabs": "1.1.13",
"@radix-ui/react-toast": "1.2.15",
"@radix-ui/react-tooltip": "1.2.8",
"@react-email/components": "1.0.8",
"@react-email/render": "2.0.4",
"@react-email/tailwind": "2.0.5",
"@react-email/body": "0.3.0",
"@react-email/components": "1.0.12",
"@react-email/render": "2.0.8",
"@react-email/tailwind": "2.0.7",
"@simplewebauthn/browser": "13.3.0",
"@simplewebauthn/server": "13.3.0",
"@simplewebauthn/server": "13.3.1",
"@tailwindcss/forms": "0.5.11",
"@tanstack/react-query": "5.90.21",
"@tanstack/react-query": "5.100.14",
"@tanstack/react-table": "8.21.3",
"@xterm/addon-fit": "^0.11.0",
"@xterm/addon-web-links": "^0.12.0",
"@xterm/xterm": "^6.0.0",
"arctic": "3.7.0",
"axios": "1.15.0",
"axios": "1.16.1",
"better-sqlite3": "11.9.1",
"canvas-confetti": "1.9.4",
"class-variance-authority": "0.7.1",
@@ -80,76 +86,75 @@
"d3": "7.9.0",
"drizzle-orm": "0.45.2",
"express": "5.2.1",
"express-rate-limit": "8.3.0",
"express-rate-limit": "8.5.2",
"glob": "13.0.6",
"helmet": "8.1.0",
"http-errors": "2.0.1",
"input-otp": "1.4.2",
"ioredis": "5.10.0",
"ioredis": "5.10.1",
"jmespath": "0.16.0",
"js-yaml": "4.1.1",
"jsonwebtoken": "9.0.3",
"lucide-react": "0.577.0",
"maxmind": "5.0.5",
"maxmind": "5.0.6",
"moment": "2.30.1",
"next": "15.5.15",
"next-intl": "4.8.3",
"next": "16.2.6",
"next-intl": "4.12.0",
"next-themes": "0.4.6",
"nextjs-toploader": "3.9.17",
"node-cache": "5.1.2",
"nodemailer": "8.0.5",
"nodemailer": "8.0.9",
"oslo": "1.2.1",
"pg": "8.20.0",
"posthog-node": "5.28.0",
"posthog-node": "5.34.1",
"qrcode.react": "4.2.0",
"react": "19.2.4",
"react": "19.2.6",
"react-day-picker": "9.14.0",
"react-dom": "19.2.4",
"react-dom": "19.2.6",
"react-easy-sort": "1.8.0",
"react-hook-form": "7.71.2",
"react-hook-form": "7.75.0",
"react-icons": "5.6.0",
"recharts": "2.15.4",
"recharts": "3.8.1",
"reodotdev": "1.1.0",
"resend": "6.9.2",
"semver": "7.7.4",
"semver": "7.8.1",
"sshpk": "1.18.0",
"stripe": "20.4.1",
"swagger-ui-express": "5.0.1",
"tailwind-merge": "3.5.0",
"tailwind-merge": "3.6.0",
"topojson-client": "3.1.0",
"tw-animate-css": "1.4.0",
"use-debounce": "10.1.0",
"uuid": "13.0.0",
"use-debounce": "10.1.1",
"uuid": "14.0.0",
"vaul": "1.1.2",
"visionscarto-world-atlas": "1.0.0",
"winston": "3.19.0",
"winston-daily-rotate-file": "5.0.0",
"ws": "8.19.0",
"yaml": "2.8.3",
"ws": "8.20.1",
"yaml": "2.9.0",
"yargs": "18.0.0",
"zod": "4.3.6",
"zod": "4.4.3",
"zod-validation-error": "5.0.0"
},
"devDependencies": {
"@dotenvx/dotenvx": "1.54.1",
"@dotenvx/dotenvx": "1.69.1",
"@esbuild-plugins/tsconfig-paths": "0.1.2",
"@react-email/preview-server": "5.2.10",
"@tailwindcss/postcss": "4.2.2",
"@tanstack/react-query-devtools": "5.91.3",
"@react-email/ui": "^6.5.0",
"@tailwindcss/postcss": "4.3.0",
"@tanstack/react-query-devtools": "5.100.10",
"@types/better-sqlite3": "7.6.13",
"@types/cookie-parser": "1.4.10",
"@types/cors": "2.8.19",
"@types/crypto-js": "4.2.2",
"@types/d3": "7.4.3",
"@types/express": "5.0.6",
"@types/express-session": "1.18.2",
"@types/express-session": "1.19.0",
"@types/jmespath": "0.15.2",
"@types/js-yaml": "4.0.9",
"@types/jsonwebtoken": "9.0.10",
"@types/node": "25.3.5",
"@types/nodemailer": "7.0.11",
"@types/node": "25.9.1",
"@types/nodemailer": "8.0.0",
"@types/nprogress": "0.2.3",
"@types/pg": "8.18.0",
"@types/pg": "8.20.0",
"@types/react": "19.2.14",
"@types/react-dom": "19.2.3",
"@types/semver": "7.7.1",
@@ -160,21 +165,22 @@
"@types/yargs": "17.0.35",
"babel-plugin-react-compiler": "1.0.0",
"drizzle-kit": "0.31.10",
"esbuild": "0.27.4",
"esbuild-node-externals": "1.20.1",
"eslint": "10.0.3",
"eslint-config-next": "16.1.7",
"postcss": "8.5.8",
"prettier": "3.8.1",
"react-email": "5.2.10",
"tailwindcss": "4.2.2",
"tsc-alias": "1.8.16",
"tsx": "4.21.0",
"typescript": "5.9.3",
"typescript-eslint": "8.56.1"
"esbuild": "0.28.0",
"esbuild-node-externals": "1.22.0",
"eslint": "10.4.0",
"eslint-config-next": "16.2.6",
"postcss": "8.5.14",
"prettier": "3.8.3",
"react-email": "6.5.0",
"tailwindcss": "4.3.0",
"tsc-alias": "1.8.17",
"tsx": "4.22.0",
"typescript": "6.0.3",
"typescript-eslint": "8.60.0"
},
"overrides": {
"esbuild": "0.27.4",
"dompurify": "3.3.2"
"esbuild": "0.28.0",
"dompurify": "3.4.0",
"postcss": "8.5.14"
}
}

View File

@@ -5,6 +5,7 @@ import { and, eq, inArray } from "drizzle-orm";
import createHttpError from "http-errors";
import HttpCode from "@server/types/HttpCode";
import { getUserOrgRoleIds } from "@server/lib/userOrgRoles";
import logger from "@server/logger";
export enum ActionsEnum {
createOrgUser = "createOrgUser",
@@ -148,11 +149,36 @@ export enum ActionsEnum {
updateAlertRule = "updateAlertRule",
deleteAlertRule = "deleteAlertRule",
listAlertRules = "listAlertRules",
listOrgLabels = "listOrgLabels",
createOrgLabel = "createOrgLabel",
updateOrgLabel = "updateOrgLabel",
deleteOrgLabel = "deleteOrgLabel",
attachLabelToItem = "attachLabelToItem",
detachLabelFromItem = "detachLabelFromItem",
getAlertRule = "getAlertRule",
createHealthCheck = "createHealthCheck",
updateHealthCheck = "updateHealthCheck",
deleteHealthCheck = "deleteHealthCheck",
listHealthChecks = "listHealthChecks"
listHealthChecks = "listHealthChecks",
createBrowserGatewayTarget = "createBrowserGatewayTarget",
updateBrowserGatewayTarget = "updateBrowserGatewayTarget",
deleteBrowserGatewayTarget = "deleteBrowserGatewayTarget",
getBrowserGatewayTarget = "getBrowserGatewayTarget",
listBrowserGatewayTargets = "listBrowserGatewayTargets",
listResourcePolicies = "listResourcePolicies",
getResourcePolicy = "getResourcePolicy",
createResourcePolicy = "createResourcePolicy",
updateResourcePolicy = "updateResourcePolicy",
deleteResourcePolicy = "deleteResourcePolicy",
listResourcePolicyRoles = "listResourcePolicyRoles",
setResourcePolicyRoles = "setResourcePolicyRoles",
listResourcePolicyUsers = "listResourcePolicyUsers",
setResourcePolicyUsers = "setResourcePolicyUsers",
setResourcePolicyPassword = "setResourcePolicyPassword",
setResourcePolicyPincode = "setResourcePolicyPincode",
setResourcePolicyHeaderAuth = "setResourcePolicyHeaderAuth",
setResourcePolicyWhitelist = "setResourcePolicyWhitelist",
setResourcePolicyRules = "setResourcePolicyRules"
}
export async function checkUserActionPermission(
@@ -185,6 +211,23 @@ export async function checkUserActionPermission(
}
}
// If no direct permission, check role-based permission (any of user's roles)
const roleActionPermission = await db
.select()
.from(roleActions)
.where(
and(
eq(roleActions.actionId, actionId),
inArray(roleActions.roleId, userOrgRoleIds),
eq(roleActions.orgId, req.userOrgId!)
)
)
.limit(1);
if (roleActionPermission.length > 0) {
return true;
}
// Check if the user has direct permission for the action in the current org
const userActionPermission = await db
.select()
@@ -202,20 +245,7 @@ export async function checkUserActionPermission(
return true;
}
// If no direct permission, check role-based permission (any of user's roles)
const roleActionPermission = await db
.select()
.from(roleActions)
.where(
and(
eq(roleActions.actionId, actionId),
inArray(roleActions.roleId, userOrgRoleIds),
eq(roleActions.orgId, req.userOrgId!)
)
)
.limit(1);
return roleActionPermission.length > 0;
return false;
} catch (error) {
console.error("Error checking user action permission:", error);
throw createHttpError(

View File

@@ -1,6 +1,12 @@
import { join } from "path";
import { readFileSync } from "fs";
import { clients, db, resources, siteResources } from "@server/db";
import {
clients,
db,
resourcePolicies,
resources,
siteResources
} from "@server/db";
import { randomInt } from "crypto";
import { exitNodes, sites } from "@server/db";
import { eq, and } from "drizzle-orm";
@@ -107,6 +113,35 @@ export async function getUniqueResourceName(orgId: string): Promise<string> {
}
}
export async function getUniqueResourcePolicyName(
orgId: string
): Promise<string> {
let loops = 0;
while (true) {
if (loops > 100) {
throw new Error("Could not generate a unique name");
}
const name = generateName();
const policyCount = await db
.select({
niceId: resourcePolicies.niceId,
orgId: resourcePolicies.orgId
})
.from(resourcePolicies)
.where(
and(
eq(resourcePolicies.niceId, name),
eq(resourcePolicies.orgId, orgId)
)
);
if (policyCount.length === 0) {
return name;
}
loops++;
}
}
export async function getUniqueSiteResourceName(
orgId: string
): Promise<string> {

View File

@@ -580,6 +580,24 @@ export const trialNotifications = pgTable("trialNotifications", {
sentAt: bigint("sentAt", { mode: "number" }).notNull()
});
export const browserGatewayTarget = pgTable("browserGatewayTarget", {
browserGatewayTargetId: serial("browserGatewayTargetId").primaryKey(),
resourceId: integer("resourceId")
.references(() => resources.resourceId, {
onDelete: "cascade"
})
.notNull(),
siteId: integer("siteId")
.references(() => sites.siteId, {
onDelete: "cascade"
})
.notNull(),
authToken: varchar("authToken").notNull(),
type: varchar("type").notNull(), // "ssh", "rdp", "vnc"
destination: varchar("destination").notNull(),
destinationPort: integer("destinationPort").notNull()
});
export type Approval = InferSelectModel<typeof approvals>;
export type Limit = InferSelectModel<typeof limits>;
export type Account = InferSelectModel<typeof account>;
@@ -627,3 +645,6 @@ export type AlertEmailRecipients = InferSelectModel<
>;
export type AlertWebhookActions = InferSelectModel<typeof alertWebhookActions>;
export type TrialNotification = InferSelectModel<typeof trialNotifications>;
export type BrowserGatewayTarget = InferSelectModel<
typeof browserGatewayTarget
>;

View File

@@ -65,7 +65,12 @@ export const orgs = pgTable("orgs", {
sshCaPrivateKey: text("sshCaPrivateKey"), // Encrypted SSH CA private key (PEM format)
sshCaPublicKey: text("sshCaPublicKey"), // SSH CA public key (OpenSSH format)
isBillingOrg: boolean("isBillingOrg"),
billingOrgId: varchar("billingOrgId")
billingOrgId: varchar("billingOrgId"),
settingsEnableGlobalNewtAutoUpdate: boolean(
"settingsEnableGlobalNewtAutoUpdate"
)
.notNull()
.default(false)
});
export const orgDomains = pgTable("orgDomains", {
@@ -103,6 +108,10 @@ export const sites = pgTable("sites", {
lastHolePunch: bigint("lastHolePunch", { mode: "number" }),
listenPort: integer("listenPort"),
dockerSocketEnabled: boolean("dockerSocketEnabled").notNull().default(true),
autoUpdateEnabled: boolean("autoUpdateEnabled").notNull().default(false),
autoUpdateOverrideOrg: boolean("autoUpdateOverrideOrg")
.notNull()
.default(false),
status: varchar("status")
.$type<"pending" | "approved">()
.default("approved")
@@ -110,6 +119,16 @@ export const sites = pgTable("sites", {
export const resources = pgTable("resources", {
resourceId: serial("resourceId").primaryKey(),
resourcePolicyId: integer("resourcePolicyId").references(
() => resourcePolicies.resourcePolicyId,
{ onDelete: "set null" }
),
defaultResourcePolicyId: integer("defaultResourcePolicyId").references(
() => resourcePolicies.resourcePolicyId,
{
onDelete: "restrict"
}
),
resourceGuid: varchar("resourceGuid", { length: 36 })
.unique()
.notNull()
@@ -129,8 +148,6 @@ export const resources = pgTable("resources", {
ssl: boolean("ssl").notNull().default(false),
blockAccess: boolean("blockAccess").notNull().default(false),
sso: boolean("sso").notNull().default(true),
http: boolean("http").notNull().default(true),
protocol: varchar("protocol").notNull(),
proxyPort: integer("proxyPort"),
emailWhitelistEnabled: boolean("emailWhitelistEnabled")
.notNull()
@@ -147,7 +164,6 @@ export const resources = pgTable("resources", {
headers: text("headers"), // comma-separated list of headers to add to the request
proxyProtocol: boolean("proxyProtocol").notNull().default(false),
proxyProtocolVersion: integer("proxyProtocolVersion").default(1),
maintenanceModeEnabled: boolean("maintenanceModeEnabled")
.notNull()
.default(false),
@@ -159,9 +175,100 @@ export const resources = pgTable("resources", {
maintenanceEstimatedTime: text("maintenanceEstimatedTime"),
postAuthPath: text("postAuthPath"),
health: varchar("health").default("unknown"), // "healthy", "unhealthy", "unknown"
wildcard: boolean("wildcard").notNull().default(false)
wildcard: boolean("wildcard").notNull().default(false),
mode: text("mode").default("http").notNull(), // rdp, ssh, http, vnc
pamMode: varchar("pamMode", { length: 32 })
.$type<"passthrough" | "push">()
.default("passthrough"),
authDaemonMode: varchar("authDaemonMode", { length: 32 })
.$type<"site" | "remote" | "native">()
.default("site"),
authDaemonPort: integer("authDaemonPort").default(22123)
});
export const labels = pgTable("labels", {
labelId: serial("labelId").primaryKey(),
name: varchar("name").notNull(),
color: varchar("color").notNull(),
orgId: varchar("orgId")
.references(() => orgs.orgId, {
onDelete: "cascade"
})
.notNull()
});
export const siteLabels = pgTable(
"siteLabels",
{
siteLabelId: serial("siteLabelId").primaryKey(),
siteId: integer("siteId")
.references(() => sites.siteId, {
onDelete: "cascade"
})
.notNull(),
labelId: integer("labelId")
.references(() => labels.labelId, {
onDelete: "cascade"
})
.notNull()
},
(t) => [unique("site_label_uniq").on(t.siteId, t.labelId)]
);
export const resourceLabels = pgTable(
"resourceLabels",
{
resourceLabelId: serial("resourceLabelId").primaryKey(),
resourceId: integer("resourceId")
.references(() => resources.resourceId, {
onDelete: "cascade"
})
.notNull(),
labelId: integer("labelId")
.references(() => labels.labelId, {
onDelete: "cascade"
})
.notNull()
},
(t) => [unique("resource_label_uniq").on(t.resourceId, t.labelId)]
);
export const siteResourceLabels = pgTable(
"siteResourceLabels",
{
siteResourceLabelId: serial("siteResourceLabelId").primaryKey(),
siteResourceId: integer("siteResourceId")
.references(() => siteResources.siteResourceId, {
onDelete: "cascade"
})
.notNull(),
labelId: integer("labelId")
.references(() => labels.labelId, {
onDelete: "cascade"
})
.notNull()
},
(t) => [unique("site_resource_label_uniq").on(t.siteResourceId, t.labelId)]
);
export const clientLabels = pgTable(
"clientLabels",
{
clientLabelId: serial("clientLabelId").primaryKey(),
clientId: integer("clientId")
.references(() => clients.clientId, {
onDelete: "cascade"
})
.notNull(),
labelId: integer("labelId")
.references(() => labels.labelId, {
onDelete: "cascade"
})
.notNull()
},
(t) => [unique("client_label_uniq").on(t.clientId, t.labelId)]
);
export const targets = pgTable("targets", {
targetId: serial("targetId").primaryKey(),
resourceId: integer("resourceId")
@@ -196,9 +303,11 @@ export const targetHealthCheck = pgTable("targetHealthCheck", {
onDelete: "cascade"
})
.notNull(),
siteId: integer("siteId").references(() => sites.siteId, {
onDelete: "cascade"
}).notNull(),
siteId: integer("siteId")
.references(() => sites.siteId, {
onDelete: "cascade"
})
.notNull(),
name: varchar("name"),
hcEnabled: boolean("hcEnabled").notNull().default(false),
hcPath: varchar("hcPath"),
@@ -254,11 +363,11 @@ export const siteResources = pgTable("siteResources", {
niceId: varchar("niceId").notNull(),
name: varchar("name").notNull(),
ssl: boolean("ssl").notNull().default(false),
mode: varchar("mode").$type<"host" | "cidr" | "http">().notNull(), // "host" | "cidr" | "http"
mode: varchar("mode").$type<"host" | "cidr" | "http" | "ssh">().notNull(), // "host" | "cidr" | "http"
scheme: varchar("scheme").$type<"http" | "https">(), // only for when we are doing https or http mode
proxyPort: integer("proxyPort"), // only for port mode
destinationPort: integer("destinationPort"), // only for port mode
destination: varchar("destination").notNull(), // ip, cidr, hostname; validate against the mode
destination: varchar("destination"), // ip, cidr, hostname; validate against the mode
enabled: boolean("enabled").notNull().default(true),
alias: varchar("alias"),
aliasAddress: varchar("aliasAddress"),
@@ -266,8 +375,11 @@ export const siteResources = pgTable("siteResources", {
udpPortRangeString: varchar("udpPortRangeString").notNull().default("*"),
disableIcmp: boolean("disableIcmp").notNull().default(false),
authDaemonPort: integer("authDaemonPort").default(22123),
pamMode: varchar("pamMode", { length: 32 })
.$type<"passthrough" | "push">()
.default("passthrough"),
authDaemonMode: varchar("authDaemonMode", { length: 32 })
.$type<"site" | "remote">()
.$type<"site" | "remote" | "native">()
.default("site"),
domainId: varchar("domainId").references(() => domains.domainId, {
onDelete: "set null"
@@ -521,6 +633,38 @@ export const userResources = pgTable("userResources", {
.references(() => resources.resourceId, { onDelete: "cascade" })
});
export const rolePolicies = pgTable("rolePolicies", {
roleId: integer("roleId")
.notNull()
.references(() => roles.roleId, { onDelete: "cascade" }),
resourcePolicyId: integer("resourcePolicyId")
.notNull()
.references(() => resourcePolicies.resourcePolicyId, {
onDelete: "cascade"
})
});
export const userPolicies = pgTable("userPolicies", {
userId: varchar("userId")
.notNull()
.references(() => users.userId, { onDelete: "cascade" }),
resourcePolicyId: integer("resourcePolicyId")
.notNull()
.references(() => resourcePolicies.resourcePolicyId, {
onDelete: "cascade"
})
});
export const resourcePolicyWhiteList = pgTable("resourcePolicyWhitelist", {
whitelistId: serial("id").primaryKey(),
email: varchar("email").notNull(),
resourcePolicyId: integer("resourcePolicyId")
.notNull()
.references(() => resourcePolicies.resourcePolicyId, {
onDelete: "cascade"
})
});
export const userInvites = pgTable("userInvites", {
inviteId: varchar("inviteId").primaryKey(),
orgId: varchar("orgId")
@@ -586,6 +730,40 @@ export const resourceHeaderAuthExtendedCompatibility = pgTable(
}
);
export const resourcePolicyPincode = pgTable("resourcePolicyPincode", {
pincodeId: serial("pincodeId").primaryKey(),
pincodeHash: varchar("pincodeHash").notNull(),
digitLength: integer("digitLength").notNull(),
resourcePolicyId: integer("resourcePolicyId")
.notNull()
.references(() => resourcePolicies.resourcePolicyId, {
onDelete: "cascade"
})
});
export const resourcePolicyPassword = pgTable("resourcePolicyPassword", {
passwordId: serial("passwordId").primaryKey(),
passwordHash: varchar("passwordHash").notNull(),
resourcePolicyId: integer("resourcePolicyId")
.notNull()
.references(() => resourcePolicies.resourcePolicyId, {
onDelete: "cascade"
})
});
export const resourcePolicyHeaderAuth = pgTable("resourcePolicyHeaderAuth", {
headerAuthId: serial("headerAuthId").primaryKey(),
headerAuthHash: varchar("headerAuthHash").notNull(),
extendedCompatibility: boolean("extendedCompatibility")
.notNull()
.default(true),
resourcePolicyId: integer("resourcePolicyId")
.notNull()
.references(() => resourcePolicies.resourcePolicyId, {
onDelete: "cascade"
})
});
export const resourceAccessToken = pgTable("resourceAccessToken", {
accessTokenId: varchar("accessTokenId").primaryKey(),
orgId: varchar("orgId")
@@ -679,6 +857,43 @@ export const resourceRules = pgTable("resourceRules", {
value: varchar("value").notNull()
});
export const resourcePolicyRules = pgTable("resourcePolicyRules", {
ruleId: serial("ruleId").primaryKey(),
resourcePolicyId: integer("resourcePolicyId")
.notNull()
.references(() => resourcePolicies.resourcePolicyId, {
onDelete: "cascade"
}),
enabled: boolean("enabled").notNull().default(true),
priority: integer("priority").notNull(),
action: varchar("action").$type<"ACCEPT" | "DROP" | "PASS">().notNull(),
match: varchar("match").$type<"CIDR" | "PATH" | "IP">().notNull(),
value: varchar("value").notNull()
});
export const resourcePolicies = pgTable("resourcePolicies", {
resourcePolicyId: serial("resourcePolicyId").primaryKey(),
sso: boolean("sso").notNull().default(true),
applyRules: boolean("applyRules").notNull().default(false),
scope: varchar("scope")
.$type<"global" | "resource">()
.notNull()
.default("global"),
emailWhitelistEnabled: boolean("emailWhitelistEnabled")
.notNull()
.default(false),
idpId: integer("idpId").references(() => idp.idpId, {
onDelete: "set null"
}),
niceId: text("niceId").notNull(),
name: varchar("name").notNull(),
orgId: varchar("orgId")
.references(() => orgs.orgId, {
onDelete: "cascade"
})
.notNull()
});
export const supporterKey = pgTable("supporterKey", {
keyId: serial("keyId").primaryKey(),
key: varchar("key").notNull(),
@@ -1097,19 +1312,30 @@ export const roundTripMessageTracker = pgTable("roundTripMessageTracker", {
complete: boolean("complete").notNull().default(false)
});
export const statusHistory = pgTable("statusHistory", {
id: serial("id").primaryKey(),
entityType: varchar("entityType").notNull(),
entityId: integer("entityId").notNull(),
orgId: varchar("orgId")
.notNull()
.references(() => orgs.orgId, { onDelete: "cascade" }),
status: varchar("status").notNull(),
timestamp: integer("timestamp").notNull(),
}, (table) => [
index("idx_statusHistory_entity").on(table.entityType, table.entityId, table.timestamp),
index("idx_statusHistory_org_timestamp").on(table.orgId, table.timestamp),
]);
export const statusHistory = pgTable(
"statusHistory",
{
id: serial("id").primaryKey(),
entityType: varchar("entityType").notNull(),
entityId: integer("entityId").notNull(),
orgId: varchar("orgId")
.notNull()
.references(() => orgs.orgId, { onDelete: "cascade" }),
status: varchar("status").notNull(),
timestamp: integer("timestamp").notNull()
},
(table) => [
index("idx_statusHistory_entity").on(
table.entityType,
table.entityId,
table.timestamp
),
index("idx_statusHistory_org_timestamp").on(
table.orgId,
table.timestamp
)
]
);
export type Org = InferSelectModel<typeof orgs>;
export type User = InferSelectModel<typeof users>;
@@ -1179,3 +1405,7 @@ export type RoundTripMessageTracker = InferSelectModel<
>;
export type Network = InferSelectModel<typeof networks>;
export type StatusHistory = InferSelectModel<typeof statusHistory>;
export type Label = InferSelectModel<typeof labels>;
export type ResourcePolicy = InferSelectModel<typeof resourcePolicies>;
export type RolePolicy = InferSelectModel<typeof rolePolicies>;
export type UserPolicy = InferSelectModel<typeof userPolicies>;

View File

@@ -17,10 +17,13 @@ import {
resourceHeaderAuth,
ResourceHeaderAuth,
resourceRules,
resourcePolicyRules,
resources,
roleResources,
rolePolicies,
sessions,
userResources,
userPolicies,
users,
ResourceHeaderAuthExtendedCompatibility,
resourceHeaderAuthExtendedCompatibility
@@ -154,58 +157,126 @@ export async function getRoleName(roleId: number): Promise<string | null> {
}
/**
* Check if role has access to resource
* Check if role has access to resource (direct or via resource policy)
*/
export async function getRoleResourceAccess(
resourceId: number,
roleIds: number[]
) {
const roleResourceAccess = await db
.select()
.from(roleResources)
.where(
and(
eq(roleResources.resourceId, resourceId),
inArray(roleResources.roleId, roleIds)
const [direct, viaPolicies] = await Promise.all([
db
.select()
.from(roleResources)
.where(
and(
eq(roleResources.resourceId, resourceId),
inArray(roleResources.roleId, roleIds)
)
),
db
.select({
roleId: rolePolicies.roleId,
resourcePolicyId: rolePolicies.resourcePolicyId
})
.from(rolePolicies)
.innerJoin(
resources,
eq(resources.resourcePolicyId, rolePolicies.resourcePolicyId)
)
);
.where(
and(
eq(resources.resourceId, resourceId),
inArray(rolePolicies.roleId, roleIds)
)
)
]);
return roleResourceAccess.length > 0 ? roleResourceAccess : null;
const combined = [...direct, ...viaPolicies];
return combined.length > 0 ? combined : null;
}
/**
* Check if user has direct access to resource
* Check if user has access to resource (direct or via resource policy)
*/
export async function getUserResourceAccess(
userId: string,
resourceId: number
) {
const userResourceAccess = await db
.select()
.from(userResources)
.where(
and(
eq(userResources.userId, userId),
eq(userResources.resourceId, resourceId)
const [direct, viaPolicies] = await Promise.all([
db
.select()
.from(userResources)
.where(
and(
eq(userResources.userId, userId),
eq(userResources.resourceId, resourceId)
)
)
)
.limit(1);
.limit(1),
db
.select({
userId: userPolicies.userId,
resourcePolicyId: userPolicies.resourcePolicyId
})
.from(userPolicies)
.innerJoin(
resources,
eq(resources.resourcePolicyId, userPolicies.resourcePolicyId)
)
.where(
and(
eq(resources.resourceId, resourceId),
eq(userPolicies.userId, userId)
)
)
.limit(1)
]);
return userResourceAccess.length > 0 ? userResourceAccess[0] : null;
return direct[0] ?? viaPolicies[0] ?? null;
}
/**
* Get resource rules for a given resource
* Get resource rules for a given resource (direct and via resource policy)
*/
export async function getResourceRules(
resourceId: number
): Promise<ResourceRule[]> {
const rules = await db
.select()
.from(resourceRules)
.where(eq(resourceRules.resourceId, resourceId));
const [directRules, policyRules] = await Promise.all([
db
.select()
.from(resourceRules)
.where(eq(resourceRules.resourceId, resourceId)),
db
.select({
ruleId: resourcePolicyRules.ruleId,
resourceId: sql<number>`${resourceId}`,
enabled: resourcePolicyRules.enabled,
priority: resourcePolicyRules.priority,
action: resourcePolicyRules.action,
match: resourcePolicyRules.match,
value: resourcePolicyRules.value
})
.from(resourcePolicyRules)
.innerJoin(
resources,
eq(
resources.resourcePolicyId,
resourcePolicyRules.resourcePolicyId
)
)
.where(eq(resources.resourceId, resourceId))
]);
return rules;
const maxDirectPriority = directRules.reduce(
(max, r) => Math.max(max, r.priority),
0
);
const offsetPolicyRules = policyRules.map((r) => ({
...r,
priority: maxDirectPriority + r.priority
}));
return [...directRules, ...offsetPolicyRules] as ResourceRule[];
}
/**

View File

@@ -588,6 +588,26 @@ export const trialNotifications = sqliteTable("trialNotifications", {
sentAt: integer("sentAt").notNull()
});
export const browserGatewayTarget = sqliteTable("browserGatewayTarget", {
browserGatewayTargetId: integer("browserGatewayTargetId").primaryKey({
autoIncrement: true
}),
resourceId: integer("resourceId")
.references(() => resources.resourceId, {
onDelete: "cascade"
})
.notNull(),
siteId: integer("siteId")
.references(() => sites.siteId, {
onDelete: "cascade"
})
.notNull(),
authToken: text("authToken").notNull(),
type: text("type").notNull(), // "ssh", "rdp", "vnc"
destination: text("destination").notNull(),
destinationPort: integer("destinationPort").notNull()
});
export type Approval = InferSelectModel<typeof approvals>;
export type Limit = InferSelectModel<typeof limits>;
export type Account = InferSelectModel<typeof account>;
@@ -627,3 +647,6 @@ export type AlertEmailAction = InferSelectModel<typeof alertEmailActions>;
export type AlertEmailRecipient = InferSelectModel<typeof alertEmailRecipients>;
export type AlertWebhookAction = InferSelectModel<typeof alertWebhookActions>;
export type TrialNotification = InferSelectModel<typeof trialNotifications>;
export type BrowserGatewayTarget = InferSelectModel<
typeof browserGatewayTarget
>;

View File

@@ -62,7 +62,13 @@ export const orgs = sqliteTable("orgs", {
sshCaPrivateKey: text("sshCaPrivateKey"), // Encrypted SSH CA private key (PEM format)
sshCaPublicKey: text("sshCaPublicKey"), // SSH CA public key (OpenSSH format)
isBillingOrg: integer("isBillingOrg", { mode: "boolean" }),
billingOrgId: text("billingOrgId")
billingOrgId: text("billingOrgId"),
settingsEnableGlobalNewtAutoUpdate: integer(
"settingsEnableGlobalNewtAutoUpdate",
{ mode: "boolean" }
)
.notNull()
.default(false)
});
export const userDomains = sqliteTable("userDomains", {
@@ -116,11 +122,29 @@ export const sites = sqliteTable("sites", {
dockerSocketEnabled: integer("dockerSocketEnabled", { mode: "boolean" })
.notNull()
.default(true),
autoUpdateEnabled: integer("autoUpdateEnabled", { mode: "boolean" })
.notNull()
.default(false),
autoUpdateOverrideOrg: integer("autoUpdateOverrideOrg", {
mode: "boolean"
})
.notNull()
.default(false),
status: text("status").$type<"pending" | "approved">().default("approved")
});
export const resources = sqliteTable("resources", {
resourceId: integer("resourceId").primaryKey({ autoIncrement: true }),
resourcePolicyId: integer("resourcePolicyId").references(
() => resourcePolicies.resourcePolicyId,
{ onDelete: "set null" }
),
defaultResourcePolicyId: integer("defaultResourcePolicyId").references(
() => resourcePolicies.resourcePolicyId,
{
onDelete: "restrict"
}
),
resourceGuid: text("resourceGuid", { length: 36 })
.unique()
.notNull()
@@ -142,8 +166,6 @@ export const resources = sqliteTable("resources", {
.notNull()
.default(false),
sso: integer("sso", { mode: "boolean" }).notNull().default(true),
http: integer("http", { mode: "boolean" }).notNull().default(true),
protocol: text("protocol").notNull(),
proxyPort: integer("proxyPort"),
emailWhitelistEnabled: integer("emailWhitelistEnabled", { mode: "boolean" })
.notNull()
@@ -166,7 +188,6 @@ export const resources = sqliteTable("resources", {
.notNull()
.default(false),
proxyProtocolVersion: integer("proxyProtocolVersion").default(1),
maintenanceModeEnabled: integer("maintenanceModeEnabled", {
mode: "boolean"
})
@@ -180,9 +201,106 @@ export const resources = sqliteTable("resources", {
maintenanceEstimatedTime: text("maintenanceEstimatedTime"),
postAuthPath: text("postAuthPath"),
health: text("health").default("unknown"), // "healthy", "unhealthy", "unknown"
wildcard: integer("wildcard", { mode: "boolean" }).notNull().default(false)
wildcard: integer("wildcard", { mode: "boolean" }).notNull().default(false),
mode: text("mode").default("http").notNull(), // rdp, ssh, http, vnc
pamMode: text("pamMode")
.$type<"passthrough" | "push">()
.default("passthrough"),
authDaemonMode: text("authDaemonMode")
.$type<"site" | "remote" | "native">()
.default("site"),
authDaemonPort: integer("authDaemonPort").default(22123)
});
export const labels = sqliteTable("labels", {
labelId: integer("labelId").primaryKey({ autoIncrement: true }),
name: text("name").notNull(),
color: text("color").notNull(),
orgId: text("orgId")
.references(() => orgs.orgId, {
onDelete: "cascade"
})
.notNull()
});
export const siteLabels = sqliteTable(
"siteLabels",
{
siteLabelId: integer("siteLabelId").primaryKey({ autoIncrement: true }),
siteId: integer("siteId")
.references(() => sites.siteId, {
onDelete: "cascade"
})
.notNull(),
labelId: integer("labelId")
.references(() => labels.labelId, {
onDelete: "cascade"
})
.notNull()
},
(t) => [unique("site_label_uniq").on(t.siteId, t.labelId)]
);
export const resourceLabels = sqliteTable(
"resourceLabels",
{
resourceLabelId: integer("resourceLabelId").primaryKey({
autoIncrement: true
}),
resourceId: integer("resourceId")
.references(() => resources.resourceId, {
onDelete: "cascade"
})
.notNull(),
labelId: integer("labelId")
.references(() => labels.labelId, {
onDelete: "cascade"
})
.notNull()
},
(t) => [unique("resource_label_uniq").on(t.resourceId, t.labelId)]
);
export const siteResourceLabels = sqliteTable(
"siteResourceLabels",
{
siteResourceLabelId: integer("siteResourceLabelId").primaryKey({
autoIncrement: true
}),
siteResourceId: integer("siteResourceId")
.references(() => siteResources.siteResourceId, {
onDelete: "cascade"
})
.notNull(),
labelId: integer("labelId")
.references(() => labels.labelId, {
onDelete: "cascade"
})
.notNull()
},
(t) => [unique("site_resource_label_uniq").on(t.siteResourceId, t.labelId)]
);
export const clientLabels = sqliteTable(
"clientLabels",
{
clientLabelId: integer("clientLabelId").primaryKey({
autoIncrement: true
}),
clientId: integer("clientId")
.references(() => clients.clientId, {
onDelete: "cascade"
})
.notNull(),
labelId: integer("labelId")
.references(() => labels.labelId, {
onDelete: "cascade"
})
.notNull()
},
(t) => [unique("client_label_uniq").on(t.clientId, t.labelId)]
);
export const targets = sqliteTable("targets", {
targetId: integer("targetId").primaryKey({ autoIncrement: true }),
resourceId: integer("resourceId")
@@ -219,9 +337,11 @@ export const targetHealthCheck = sqliteTable("targetHealthCheck", {
onDelete: "cascade"
})
.notNull(),
siteId: integer("siteId").references(() => sites.siteId, {
onDelete: "cascade"
}).notNull(),
siteId: integer("siteId")
.references(() => sites.siteId, {
onDelete: "cascade"
})
.notNull(),
name: text("name"),
hcEnabled: integer("hcEnabled", { mode: "boolean" })
.notNull()
@@ -281,11 +401,11 @@ export const siteResources = sqliteTable("siteResources", {
niceId: text("niceId").notNull(),
name: text("name").notNull(),
ssl: integer("ssl", { mode: "boolean" }).notNull().default(false),
mode: text("mode").$type<"host" | "cidr" | "http">().notNull(), // "host" | "cidr" | "http"
mode: text("mode").$type<"host" | "cidr" | "http" | "ssh">().notNull(), // "host" | "cidr" | "http"
scheme: text("scheme").$type<"http" | "https">(), // only for when we are doing https or http mode
proxyPort: integer("proxyPort"), // only for port mode
destinationPort: integer("destinationPort"), // only for port mode
destination: text("destination").notNull(), // ip, cidr, hostname
destination: text("destination"), // ip, cidr, hostname
enabled: integer("enabled", { mode: "boolean" }).notNull().default(true),
alias: text("alias"),
aliasAddress: text("aliasAddress"),
@@ -295,8 +415,11 @@ export const siteResources = sqliteTable("siteResources", {
.notNull()
.default(false),
authDaemonPort: integer("authDaemonPort").default(22123),
pamMode: text("pamMode")
.$type<"passthrough" | "push">()
.default("passthrough"),
authDaemonMode: text("authDaemonMode")
.$type<"site" | "remote">()
.$type<"site" | "remote" | "native">()
.default("site"),
domainId: text("domainId").references(() => domains.domainId, {
onDelete: "set null"
@@ -909,6 +1032,47 @@ export const resourceHeaderAuth = sqliteTable("resourceHeaderAuth", {
headerAuthHash: text("headerAuthHash").notNull()
});
export const resourcePolicyPincode = sqliteTable("resourcePolicyPincode", {
pincodeId: integer("pincodeId").primaryKey({ autoIncrement: true }),
pincodeHash: text("pincodeHash").notNull(),
digitLength: integer("digitLength").notNull(),
resourcePolicyId: integer("resourcePolicyId")
.notNull()
.references(() => resourcePolicies.resourcePolicyId, {
onDelete: "cascade"
})
});
export const resourcePolicyPassword = sqliteTable("resourcePolicyPassword", {
passwordId: integer("passwordId").primaryKey({ autoIncrement: true }),
passwordHash: text("passwordHash").notNull(),
resourcePolicyId: integer("resourcePolicyId")
.notNull()
.references(() => resourcePolicies.resourcePolicyId, {
onDelete: "cascade"
})
});
export const resourcePolicyHeaderAuth = sqliteTable(
"resourcePolicyHeaderAuth",
{
headerAuthId: integer("headerAuthId").primaryKey({
autoIncrement: true
}),
headerAuthHash: text("headerAuthHash").notNull(),
extendedCompatibility: integer("extendedCompatibility", {
mode: "boolean"
})
.notNull()
.default(true),
resourcePolicyId: integer("resourcePolicyId")
.notNull()
.references(() => resourcePolicies.resourcePolicyId, {
onDelete: "cascade"
})
}
);
export const resourceHeaderAuthExtendedCompatibility = sqliteTable(
"resourceHeaderAuthExtendedCompatibility",
{
@@ -1023,6 +1187,77 @@ export const resourceRules = sqliteTable("resourceRules", {
value: text("value").notNull()
});
export const rolePolicies = sqliteTable("rolePolicies", {
roleId: integer("roleId")
.notNull()
.references(() => roles.roleId, { onDelete: "cascade" }),
resourcePolicyId: integer("resourcePolicyId")
.notNull()
.references(() => resourcePolicies.resourcePolicyId, {
onDelete: "cascade"
})
});
export const userPolicies = sqliteTable("userPolicies", {
userId: text("userId")
.notNull()
.references(() => users.userId, { onDelete: "cascade" }),
resourcePolicyId: integer("resourcePolicyId")
.notNull()
.references(() => resourcePolicies.resourcePolicyId, {
onDelete: "cascade"
})
});
export const resourcePolicyWhiteList = sqliteTable("resourcePolicyWhitelist", {
whitelistId: integer("id").primaryKey({ autoIncrement: true }),
email: text("email").notNull(),
resourcePolicyId: integer("resourcePolicyId")
.notNull()
.references(() => resourcePolicies.resourcePolicyId, {
onDelete: "cascade"
})
});
export const resourcePolicyRules = sqliteTable("resourcePolicyRules", {
ruleId: integer("ruleId").primaryKey({ autoIncrement: true }),
resourcePolicyId: integer("resourcePolicyId")
.notNull()
.references(() => resourcePolicies.resourcePolicyId, {
onDelete: "cascade"
}),
enabled: integer("enabled", { mode: "boolean" }).notNull().default(true),
priority: integer("priority").notNull(),
action: text("action").$type<"ACCEPT" | "DROP" | "PASS">().notNull(),
match: text("match").$type<"CIDR" | "PATH" | "IP">().notNull(),
value: text("value").notNull()
});
export const resourcePolicies = sqliteTable("resourcePolicies", {
resourcePolicyId: integer("resourcePolicyId").primaryKey(),
sso: integer("sso", { mode: "boolean" }).notNull().default(true),
applyRules: integer("applyRules", { mode: "boolean" })
.notNull()
.default(false),
scope: text("scope")
.$type<"global" | "resource">()
.notNull()
.default("global"),
emailWhitelistEnabled: integer("emailWhitelistEnabled", { mode: "boolean" })
.notNull()
.default(false),
niceId: text("niceId").notNull(),
idpId: integer("idpId").references(() => idp.idpId, {
onDelete: "set null"
}),
name: text("name").notNull(),
orgId: text("orgId")
.references(() => orgs.orgId, {
onDelete: "cascade"
})
.notNull()
});
export const supporterKey = sqliteTable("supporterKey", {
keyId: integer("keyId").primaryKey({ autoIncrement: true }),
key: text("key").notNull(),
@@ -1196,19 +1431,30 @@ export const roundTripMessageTracker = sqliteTable("roundTripMessageTracker", {
complete: integer("complete", { mode: "boolean" }).notNull().default(false)
});
export const statusHistory = sqliteTable("statusHistory", {
id: integer("id").primaryKey({ autoIncrement: true }),
entityType: text("entityType").notNull(), // "site" | "healthCheck"
entityId: integer("entityId").notNull(), // siteId or targetHealthCheckId
orgId: text("orgId")
.notNull()
.references(() => orgs.orgId, { onDelete: "cascade" }),
status: text("status").notNull(), // "online"/"offline" for sites; "healthy"/"unhealthy"/"unknown" for healthChecks
timestamp: integer("timestamp").notNull(), // unix epoch seconds
}, (table) => [
index("idx_statusHistory_entity").on(table.entityType, table.entityId, table.timestamp),
index("idx_statusHistory_org_timestamp").on(table.orgId, table.timestamp),
]);
export const statusHistory = sqliteTable(
"statusHistory",
{
id: integer("id").primaryKey({ autoIncrement: true }),
entityType: text("entityType").notNull(), // "site" | "healthCheck"
entityId: integer("entityId").notNull(), // siteId or targetHealthCheckId
orgId: text("orgId")
.notNull()
.references(() => orgs.orgId, { onDelete: "cascade" }),
status: text("status").notNull(), // "online"/"offline" for sites; "healthy"/"unhealthy"/"unknown" for healthChecks
timestamp: integer("timestamp").notNull() // unix epoch seconds
},
(table) => [
index("idx_statusHistory_entity").on(
table.entityType,
table.entityId,
table.timestamp
),
index("idx_statusHistory_org_timestamp").on(
table.orgId,
table.timestamp
)
]
);
export type Org = InferSelectModel<typeof orgs>;
export type User = InferSelectModel<typeof users>;
@@ -1278,3 +1524,7 @@ export type RoundTripMessageTracker = InferSelectModel<
typeof roundTripMessageTracker
>;
export type StatusHistory = InferSelectModel<typeof statusHistory>;
export type Label = InferSelectModel<typeof labels>;
export type ResourcePolicy = InferSelectModel<typeof resourcePolicies>;
export type RolePolicy = InferSelectModel<typeof rolePolicies>;
export type UserPolicy = InferSelectModel<typeof userPolicies>;

View File

@@ -1,5 +1,5 @@
#! /usr/bin/env node
import "./extendZod.ts";
import "./extendZod";
import { runSetupFunctions } from "./setup";
import { createApiServer } from "./apiServer";

View File

@@ -24,10 +24,14 @@ export enum TierFeature {
DomainNamespaces = "domainNamespaces", // handle downgrade by removing custom domain namespaces
StandaloneHealthChecks = "standaloneHealthChecks",
AlertingRules = "alertingRules",
WildcardSubdomain = "wildcardSubdomain"
WildcardSubdomain = "wildcardSubdomain",
Labels = "labels",
NewtAutoUpdate = "newtAutoUpdate",
ResourcePolicies = "resourcePolicies"
}
export const tierMatrix: Record<TierFeature, Tier[]> = {
[TierFeature.Labels]: ["tier2", "tier3", "enterprise"],
[TierFeature.OrgOidc]: ["tier1", "tier2", "tier3", "enterprise"],
[TierFeature.LoginPageDomain]: ["tier1", "tier2", "tier3", "enterprise"],
[TierFeature.DeviceApprovals]: ["tier1", "tier3", "enterprise"],
@@ -66,5 +70,7 @@ export const tierMatrix: Record<TierFeature, Tier[]> = {
[TierFeature.DomainNamespaces]: ["tier1", "tier2", "tier3", "enterprise"],
[TierFeature.StandaloneHealthChecks]: ["tier3", "enterprise"],
[TierFeature.AlertingRules]: ["tier3", "enterprise"],
[TierFeature.WildcardSubdomain]: ["tier1", "tier2", "tier3", "enterprise"]
[TierFeature.WildcardSubdomain]: ["tier1", "tier2", "tier3", "enterprise"],
[TierFeature.NewtAutoUpdate]: ["tier1", "tier2", "tier3", "enterprise"],
[TierFeature.ResourcePolicies]: ["tier3", "enterprise"]
};

View File

@@ -16,14 +16,12 @@ import logger from "@server/logger";
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";
import { handleMessagingForUpdatedSiteResource } from "@server/routers/siteResource";
import { rebuildClientAssociationsFromSiteResource } from "../rebuildClientAssociations";
@@ -106,7 +104,7 @@ export async function applyBlueprint({
site.newt.newtId,
[target],
matchingHealthcheck ? [matchingHealthcheck] : [],
result.proxyResource.protocol,
result.proxyResource.mode === "udp" ? "udp" : "tcp",
site.newt.version
);
}

View File

@@ -225,7 +225,11 @@ export async function updateClientResources(
: resourceData["udp-ports"],
fullDomain: resourceData["full-domain"] || null,
subdomain: domainInfo ? domainInfo.subdomain : null,
domainId: domainInfo ? domainInfo.domainId : null
domainId: domainInfo ? domainInfo.domainId : null,
pamMode: resourceData["auth-daemon"]?.pam || "passthrough",
authDaemonMode:
resourceData["auth-daemon"]?.mode || "native",
authDaemonPort: resourceData["auth-daemon"]?.port || 22123
})
.where(
eq(
@@ -360,8 +364,14 @@ export async function updateClientResources(
});
} else {
let aliasAddress: string | null = null;
let releaseAliasLock: (() => Promise<void>) | null = null;
if (resourceData.mode === "host" || resourceData.mode === "http") {
aliasAddress = await getNextAvailableAliasAddress(orgId, trx);
const { value, release } = await getNextAvailableAliasAddress(
orgId,
trx
);
aliasAddress = value;
releaseAliasLock = release;
}
let domainInfo:
@@ -415,10 +425,16 @@ export async function updateClientResources(
: resourceData["udp-ports"],
fullDomain: resourceData["full-domain"] || null,
subdomain: domainInfo ? domainInfo.subdomain : null,
domainId: domainInfo ? domainInfo.domainId : null
domainId: domainInfo ? domainInfo.domainId : null,
pamMode: resourceData["auth-daemon"]?.pam || "passthrough",
authDaemonMode:
resourceData["auth-daemon"]?.mode || "native",
authDaemonPort: resourceData["auth-daemon"]?.port || 22123
})
.returning();
await releaseAliasLock?.();
const siteResourceId = newResource.siteResourceId;
for (const site of allSites) {

File diff suppressed because it is too large Load Diff

View File

@@ -161,11 +161,34 @@ export const HeaderSchema = z.object({
value: z.string().min(1)
});
export const AuthDaemonSchema = z
.object({
pam: z.enum(["passthrough", "push"]).optional().default("passthrough"),
mode: z.enum(["site", "remote", "native"]).optional().default("site"),
port: z.int().min(1).max(65535).optional()
})
.refine(
(data) => {
if (data.mode === "remote") {
return data.port !== undefined;
}
return true;
},
{
path: ["port"],
message: "port is required when auth-daemon mode is 'remote'"
}
);
// Schema for individual resource
export const ResourceSchema = z
export const PublicResourceSchema = z
.object({
name: z.string().optional(),
protocol: z.enum(["http", "tcp", "udp"]).optional(),
protocol: z
.enum(["http", "tcp", "udp", "ssh", "rdp", "vnc"])
.optional(), // this was the old one and is now DEPRECATED in favor of the mode
mode: z.enum(["http", "tcp", "udp", "ssh", "rdp", "vnc"]).optional(),
policy: z.string().optional(),
ssl: z.boolean().optional(),
scheme: z.enum(["http", "https"]).optional(),
"full-domain": z.string().optional(),
@@ -177,7 +200,8 @@ export const ResourceSchema = z
"tls-server-name": z.string().optional(),
headers: z.array(HeaderSchema).optional(),
rules: z.array(RuleSchema).optional(),
maintenance: MaintenanceSchema.optional()
maintenance: MaintenanceSchema.optional(),
"auth-daemon": AuthDaemonSchema.optional()
})
.refine(
(resource) => {
@@ -185,9 +209,10 @@ export const ResourceSchema = z
return true;
}
// Otherwise, require name and protocol for full resource definition
// Otherwise, require name and protocol/mode for full resource definition
return (
resource.name !== undefined && resource.protocol !== undefined
resource.name !== undefined &&
(resource.mode !== undefined || resource.protocol !== undefined)
);
},
{
@@ -201,8 +226,8 @@ export const ResourceSchema = z
return true;
}
// If protocol is http, all targets must have method field
if (resource.protocol === "http") {
// If protocol/mode is http, all targets must have method field
if ((resource.mode ?? resource.protocol) === "http") {
return resource.targets.every(
(target) => target == null || target.method !== undefined
);
@@ -220,8 +245,9 @@ export const ResourceSchema = z
return true;
}
// If protocol is tcp or udp, no target should have method field
if (resource.protocol === "tcp" || resource.protocol === "udp") {
// If protocol/mode is tcp or udp, no target should have method field
const effectiveProtocol1 = resource.mode ?? resource.protocol;
if (effectiveProtocol1 === "tcp" || effectiveProtocol1 === "udp") {
return resource.targets.every(
(target) => target == null || target.method === undefined
);
@@ -239,8 +265,8 @@ export const ResourceSchema = z
return true;
}
// If protocol is http, it must have a full-domain
if (resource.protocol === "http") {
// If protocol/mode is http, it must have a full-domain
if ((resource.mode ?? resource.protocol) === "http") {
return (
resource["full-domain"] !== undefined &&
resource["full-domain"].length > 0
@@ -259,8 +285,9 @@ export const ResourceSchema = z
return true;
}
// If protocol is tcp or udp, it must have both proxy-port
if (resource.protocol === "tcp" || resource.protocol === "udp") {
// If protocol/mode is tcp or udp, it must have both proxy-port
const effectiveProtocol2 = resource.mode ?? resource.protocol;
if (effectiveProtocol2 === "tcp" || effectiveProtocol2 === "udp") {
return resource["proxy-port"] !== undefined;
}
return true;
@@ -277,8 +304,9 @@ export const ResourceSchema = z
return true;
}
// If protocol is tcp or udp, it must not have auth
if (resource.protocol === "tcp" || resource.protocol === "udp") {
// If protocol/mode is tcp or udp, it must not have auth
const effectiveProtocol3 = resource.mode ?? resource.protocol;
if (effectiveProtocol3 === "tcp" || effectiveProtocol3 === "udp") {
return resource.auth === undefined;
}
return true;
@@ -349,22 +377,29 @@ export const ResourceSchema = z
message:
'Wildcard full-domain must have "*" as the leftmost label only, followed by at least two valid hostname labels (e.g. "*.example.com" or "*.level1.example.com"). Patterns like "*example.com" or "level2.*.example.com" are not supported.'
}
);
)
.transform((resource) => {
// Normalize: prefer mode, fall back to protocol for backwards compatibility
if (resource.mode === undefined && resource.protocol !== undefined) {
resource.mode = resource.protocol;
}
return resource;
});
export function isTargetsOnlyResource(resource: any): boolean {
return Object.keys(resource).length === 1 && resource.targets;
}
export const ClientResourceSchema = z
export const PrivateResourceSchema = z
.object({
name: z.string().min(1).max(255),
mode: z.enum(["host", "cidr", "http"]),
mode: z.enum(["host", "cidr", "http", "ssh"]),
site: z.string().optional(), // DEPRECATED IN FAVOR OF sites
sites: z.array(z.string()).optional().default([]),
// protocol: z.enum(["tcp", "udp"]).optional(),
// proxyPort: z.int().positive().optional(),
"destination-port": z.int().positive().optional(),
destination: z.string().min(1),
destination: z.string().min(1).optional(),
// enabled: z.boolean().default(true),
"tcp-ports": portRangeStringSchema.optional().default("*"),
"udp-ports": portRangeStringSchema.optional().default("*"),
@@ -387,11 +422,31 @@ export const ClientResourceSchema = z
error: "Admin role cannot be included in roles"
}),
users: z.array(z.string()).optional().default([]),
machines: z.array(z.string()).optional().default([])
machines: z.array(z.string()).optional().default([]),
"auth-daemon": AuthDaemonSchema.optional()
})
.refine(
(data) => {
// destination is optional only for ssh+native; required for everything else
const isNativeSSH =
data.mode === "ssh" &&
(data["auth-daemon"] === undefined ||
data["auth-daemon"].mode === "native");
if (!isNativeSSH && !data.destination) {
return false;
}
return true;
},
{
path: ["destination"],
message:
"destination is required unless mode is 'ssh' with auth-daemon mode 'native'"
}
)
.refine(
(data) => {
if (data.mode === "host") {
if (!data.destination) return true; // caught by the destination-required refine
// Check if it's a valid IP address using zod (v4 or v6)
const isValidIP = z
.union([z.ipv4(), z.ipv6()])
@@ -419,6 +474,7 @@ export const ClientResourceSchema = z
.refine(
(data) => {
if (data.mode === "cidr") {
if (!data.destination) return true; // caught by the destination-required refine
// Check if it's a valid CIDR (v4 or v6)
const isValidCIDR = z
.union([z.cidrv4(), z.cidrv6()])
@@ -436,19 +492,19 @@ export const ClientResourceSchema = z
export const ConfigSchema = z
.object({
"proxy-resources": z
.record(z.string(), ResourceSchema)
.record(z.string(), PublicResourceSchema)
.optional()
.prefault({}),
"public-resources": z
.record(z.string(), ResourceSchema)
.record(z.string(), PublicResourceSchema)
.optional()
.prefault({}),
"client-resources": z
.record(z.string(), ClientResourceSchema)
.record(z.string(), PrivateResourceSchema)
.optional()
.prefault({}),
"private-resources": z
.record(z.string(), ClientResourceSchema)
.record(z.string(), PrivateResourceSchema)
.optional()
.prefault({}),
sites: z.record(z.string(), SiteSchema).optional().prefault({})
@@ -473,10 +529,13 @@ export const ConfigSchema = z
}
return data as {
"proxy-resources": Record<string, z.infer<typeof ResourceSchema>>;
"proxy-resources": Record<
string,
z.infer<typeof PublicResourceSchema>
>;
"client-resources": Record<
string,
z.infer<typeof ClientResourceSchema>
z.infer<typeof PrivateResourceSchema>
>;
sites: Record<string, z.infer<typeof SiteSchema>>;
};
@@ -615,5 +674,5 @@ export const ConfigSchema = z
// Type inference from the schema
export type Site = z.infer<typeof SiteSchema>;
export type Target = z.infer<typeof TargetSchema>;
export type Resource = z.infer<typeof ResourceSchema>;
export type Resource = z.infer<typeof PublicResourceSchema>;
export type Config = z.infer<typeof ConfigSchema>;

View File

@@ -154,8 +154,19 @@ class AdaptiveCache {
keys(): string[] {
return localCache.keys();
}
/**
* Get keys with a specific prefix
* @param prefix - Key prefix to match
* @returns Array of matching keys
*/
async keysWithPrefix(prefix: string): Promise<string[]> {
const allKeys = localCache.keys();
return allKeys.filter((key) => key.startsWith(prefix));
}
}
// Export singleton instance
export const cache = new AdaptiveCache();
export const regionalCache = cache; // Alias for compatability with the private version
export default cache;

View File

@@ -331,16 +331,8 @@ export async function calculateUserClientsForOrgs(
];
// Get next available subnet
const newSubnet = await getNextAvailableClientSubnet(
orgId,
transaction
);
if (!newSubnet) {
logger.warn(
`Skipping org ${orgId} for OLM ${olm.olmId} (user ${userId}): no available subnet found`
);
continue;
}
const { value: newSubnet, release: releaseSubnetLock } =
await getNextAvailableClientSubnet(orgId, transaction);
const subnet = newSubnet.split("/")[0];
const updatedSubnet = `${subnet}/${org.subnet.split("/")[1]}`;
@@ -370,6 +362,7 @@ export async function calculateUserClientsForOrgs(
.insert(clients)
.values(newClientData)
.returning();
await releaseSubnetLock();
existingClientCache.set(
getOrgOlmKey(orgId, olm.olmId),
newClient

View File

@@ -327,127 +327,145 @@ export function doCidrsOverlap(cidr1: string, cidr2: string): boolean {
export async function getNextAvailableClientSubnet(
orgId: string,
transaction: Transaction | typeof db = db
): Promise<string> {
return await lockManager.withLock(
`client-subnet-allocation:${orgId}`,
async () => {
const [org] = await transaction
.select()
.from(orgs)
.where(eq(orgs.orgId, orgId));
): Promise<{ value: string; release: () => Promise<void> }> {
const lockKey = `client-subnet-allocation:${orgId}`;
const acquired = await lockManager.acquireLockWithRetry(lockKey, 6000);
if (!acquired) {
throw new Error(`Failed to acquire lock: ${lockKey}`);
}
const release = () => lockManager.releaseLock(lockKey);
if (!org) {
throw new Error(`Organization with ID ${orgId} not found`);
}
try {
const [org] = await transaction
.select()
.from(orgs)
.where(eq(orgs.orgId, orgId));
if (!org.subnet) {
throw new Error(
`Organization with ID ${orgId} has no subnet defined`
);
}
const existingAddressesSites = await transaction
.select({
address: sites.address
})
.from(sites)
.where(and(isNotNull(sites.address), eq(sites.orgId, orgId)));
const existingAddressesClients = await transaction
.select({
address: clients.subnet
})
.from(clients)
.where(
and(isNotNull(clients.subnet), eq(clients.orgId, orgId))
);
const addresses = [
...existingAddressesSites.map(
(site) => `${site.address?.split("/")[0]}/32`
), // we are overriding the 32 so that we pick individual addresses in the subnet of the org for the site and the client even though they are stored with the /block_size of the org
...existingAddressesClients.map(
(client) => `${client.address.split("/")}/32`
)
].filter((address) => address !== null) as string[];
const subnet = findNextAvailableCidr(addresses, 32, org.subnet); // pick the sites address in the org
if (!subnet) {
throw new Error("No available subnets remaining in space");
}
return subnet;
if (!org) {
throw new Error(`Organization with ID ${orgId} not found`);
}
);
if (!org.subnet) {
throw new Error(
`Organization with ID ${orgId} has no subnet defined`
);
}
const existingAddressesSites = await transaction
.select({
address: sites.address
})
.from(sites)
.where(and(isNotNull(sites.address), eq(sites.orgId, orgId)));
const existingAddressesClients = await transaction
.select({
address: clients.subnet
})
.from(clients)
.where(and(isNotNull(clients.subnet), eq(clients.orgId, orgId)));
const addresses = [
...existingAddressesSites.map(
(site) => `${site.address?.split("/")[0]}/32`
), // we are overriding the 32 so that we pick individual addresses in the subnet of the org for the site and the client even though they are stored with the /block_size of the org
...existingAddressesClients.map(
(client) => `${client.address.split("/")[0]}/32`
)
].filter((address) => address !== null) as string[];
const subnet = findNextAvailableCidr(addresses, 32, org.subnet); // pick the sites address in the org
if (!subnet) {
throw new Error("No available subnets remaining in space");
}
return { value: subnet, release };
} catch (e) {
await release();
throw e;
}
}
export async function getNextAvailableAliasAddress(
orgId: string,
trx: Transaction | typeof db = db
): Promise<string> {
return await lockManager.withLock(
`alias-address-allocation:${orgId}`,
async () => {
const [org] = await trx
.select()
.from(orgs)
.where(eq(orgs.orgId, orgId));
): Promise<{ value: string; release: () => Promise<void> }> {
const lockKey = `alias-address-allocation:${orgId}`;
const acquired = await lockManager.acquireLockWithRetry(lockKey, 6000);
if (!acquired) {
throw new Error(`Failed to acquire lock: ${lockKey}`);
}
const release = () => lockManager.releaseLock(lockKey);
if (!org) {
throw new Error(`Organization with ID ${orgId} not found`);
}
try {
const [org] = await trx
.select()
.from(orgs)
.where(eq(orgs.orgId, orgId));
if (!org.subnet) {
throw new Error(
`Organization with ID ${orgId} has no subnet defined`
);
}
if (!org.utilitySubnet) {
throw new Error(
`Organization with ID ${orgId} has no utility subnet defined`
);
}
const existingAddresses = await trx
.select({
aliasAddress: siteResources.aliasAddress
})
.from(siteResources)
.where(
and(
isNotNull(siteResources.aliasAddress),
eq(siteResources.orgId, orgId)
)
);
const addresses = [
...existingAddresses.map(
(site) => `${site.aliasAddress?.split("/")[0]}/32`
),
// reserve a /29 for the dns server and other stuff
`${org.utilitySubnet.split("/")[0]}/29`
].filter((address) => address !== null) as string[];
let subnet = findNextAvailableCidr(
addresses,
32,
org.utilitySubnet
);
if (!subnet) {
throw new Error("No available subnets remaining in space");
}
// remove the cidr
subnet = subnet.split("/")[0];
return subnet;
if (!org) {
throw new Error(`Organization with ID ${orgId} not found`);
}
);
if (!org.subnet) {
throw new Error(
`Organization with ID ${orgId} has no subnet defined`
);
}
if (!org.utilitySubnet) {
throw new Error(
`Organization with ID ${orgId} has no utility subnet defined`
);
}
const existingAddresses = await trx
.select({
aliasAddress: siteResources.aliasAddress
})
.from(siteResources)
.where(
and(
isNotNull(siteResources.aliasAddress),
eq(siteResources.orgId, orgId)
)
);
const addresses = [
...existingAddresses.map(
(site) => `${site.aliasAddress?.split("/")[0]}/32`
),
// reserve a /29 for the dns server and other stuff
`${org.utilitySubnet.split("/")[0]}/29`
].filter((address) => address !== null) as string[];
let subnet = findNextAvailableCidr(addresses, 32, org.utilitySubnet);
if (!subnet) {
throw new Error("No available subnets remaining in space");
}
// remove the cidr
subnet = subnet.split("/")[0];
return { value: subnet, release };
} catch (e) {
await release();
throw e;
}
}
export async function getNextAvailableOrgSubnet(): Promise<string> {
return await lockManager.withLock("org-subnet-allocation", async () => {
export async function getNextAvailableOrgSubnet(): Promise<{
value: string;
release: () => Promise<void>;
}> {
const lockKey = "org-subnet-allocation";
const acquired = await lockManager.acquireLockWithRetry(lockKey, 6000);
if (!acquired) {
throw new Error(`Failed to acquire lock: ${lockKey}`);
}
const release = () => lockManager.releaseLock(lockKey);
try {
const existingAddresses = await db
.select({
subnet: orgs.subnet
@@ -466,8 +484,11 @@ export async function getNextAvailableOrgSubnet(): Promise<string> {
throw new Error("No available subnets remaining in space");
}
return subnet;
});
return { value: subnet, release };
} catch (e) {
await release();
throw e;
}
}
export function generateRemoteSubnets(
@@ -475,6 +496,8 @@ export function generateRemoteSubnets(
): string[] {
const remoteSubnets = allSiteResources
.filter((sr) => {
if (!sr.destination) return false;
if (sr.mode === "cidr") {
// check if its a valid CIDR using zod
const cidrSchema = z.union([z.cidrv4(), z.cidrv6()]);
@@ -496,7 +519,7 @@ export function generateRemoteSubnets(
}
return ""; // This should never be reached due to filtering, but satisfies TypeScript
})
.filter((subnet) => subnet !== ""); // Remove empty strings just to be safe
.filter((subnet): subnet is string => subnet !== "" && subnet !== null); // Remove invalid values just to be safe
// remove duplicates
return Array.from(new Set(remoteSubnets));
}
@@ -581,7 +604,7 @@ export function generateSubnetProxyTargets(
targets.push({
sourcePrefix: clientPrefix,
destPrefix: `${siteResource.aliasAddress}/32`,
rewriteTo: destination,
rewriteTo: destination!,
portRange,
disableIcmp
});
@@ -589,7 +612,7 @@ export function generateSubnetProxyTargets(
} else if (siteResource.mode == "cidr") {
targets.push({
sourcePrefix: clientPrefix,
destPrefix: siteResource.destination,
destPrefix: siteResource.destination!,
portRange,
disableIcmp
});
@@ -671,7 +694,7 @@ export async function generateSubnetProxyTargetV2(
targets.push({
sourcePrefixes: [],
destPrefix: `${siteResource.aliasAddress}/32`,
rewriteTo: destination,
rewriteTo: destination!,
portRange,
disableIcmp,
resourceId: siteResource.siteResourceId
@@ -680,7 +703,7 @@ export async function generateSubnetProxyTargetV2(
} else if (siteResource.mode == "cidr") {
targets.push({
sourcePrefixes: [],
destPrefix: siteResource.destination,
destPrefix: siteResource.destination!,
portRange,
disableIcmp,
resourceId: siteResource.siteResourceId
@@ -738,7 +761,7 @@ export async function generateSubnetProxyTargetV2(
protocol: siteResource.ssl ? "https" : "http",
httpTargets: [
{
destAddr: siteResource.destination,
destAddr: siteResource.destination!,
destPort: siteResource.destinationPort,
scheme: siteResource.scheme
}

View File

@@ -890,6 +890,9 @@ async function handleSubnetProxyTargetUpdates(
}
for (const client of removedClients) {
if (!siteResource.destination) {
continue;
}
// Check if this client still has access to another resource
// on this specific site with the same destination. We scope
// by siteId (via siteNetworks) rather than networkId because
@@ -1563,6 +1566,9 @@ async function handleMessagesForClientResources(
}
try {
if (!resource.destination) {
continue;
}
// Check if this client still has access to another resource
// on this specific site with the same destination. We scope
// by siteId (via siteNetworks) rather than networkId because

View File

@@ -0,0 +1,11 @@
export function getFirstString(value: unknown): string | undefined {
if (typeof value === "string") {
return value;
}
if (Array.isArray(value) && typeof value[0] === "string") {
return value[0];
}
return undefined;
}

View File

@@ -1,7 +1,7 @@
import { z } from "zod";
import { db, logsDb, statusHistory } from "@server/db";
import { and, eq, gte, asc } from "drizzle-orm";
import cache from "@server/lib/cache";
import { regionalCache as cache } from "#dynamic/lib/cache";
const STATUS_HISTORY_CACHE_TTL = 60; // seconds
@@ -66,7 +66,7 @@ export async function invalidateStatusHistoryCache(
entityId: number
): Promise<void> {
const prefix = `statusHistory:${entityType}:${entityId}:`;
const keys = cache.keys().filter((k) => k.startsWith(prefix));
const keys = await cache.keysWithPrefix(prefix);
if (keys.length > 0) {
await cache.del(keys);
}

View File

@@ -2,7 +2,14 @@ import { PostHog } from "posthog-node";
import config from "./config";
import { getHostMeta } from "./hostMeta";
import logger from "@server/logger";
import { alertRules, apiKeys, blueprints, db, roles, siteResources } from "@server/db";
import {
alertRules,
apiKeys,
blueprints,
db,
roles,
siteResources
} from "@server/db";
import { sites, users, orgs, resources, clients, idp } from "@server/db";
import { eq, count, notInArray, and, isNotNull, isNull } from "drizzle-orm";
import { APP_VERSION } from "./consts";
@@ -143,8 +150,7 @@ class TelemetryClient {
.select({
name: resources.name,
sso: resources.sso,
protocol: resources.protocol,
http: resources.http
mode: resources.mode
})
.from(resources);
@@ -311,7 +317,7 @@ class TelemetryClient {
(r) => r.sso
).length,
num_resources_non_http: stats.resources.filter(
(r) => !r.http
(r) => r.mode !== "http"
).length,
num_newt_sites: stats.sites.filter((s) => s.type === "newt")
.length,

View File

@@ -55,9 +55,7 @@ export async function getTraefikConfig(
resourceName: resources.name,
fullDomain: resources.fullDomain,
ssl: resources.ssl,
http: resources.http,
proxyPort: resources.proxyPort,
protocol: resources.protocol,
subdomain: resources.subdomain,
domainId: resources.domainId,
enabled: resources.enabled,
@@ -68,6 +66,7 @@ export async function getTraefikConfig(
headers: resources.headers,
proxyProtocol: resources.proxyProtocol,
proxyProtocolVersion: resources.proxyProtocolVersion,
mode: resources.mode,
// Target fields
targetId: targets.targetId,
@@ -115,8 +114,8 @@ export async function getTraefikConfig(
),
inArray(sites.type, siteTypes),
allowRawResources
? isNotNull(resources.http) // ignore the http check if allow_raw_resources is true
: eq(resources.http, true)
? inArray(resources.mode, ["http", "udp", "tcp"]) // allow all three
: eq(resources.mode, "http")
)
)
.orderBy(desc(targets.priority), targets.targetId); // stable ordering
@@ -166,9 +165,8 @@ export async function getTraefikConfig(
key: key,
fullDomain: row.fullDomain,
ssl: row.ssl,
http: row.http,
mode: row.mode,
proxyPort: row.proxyPort,
protocol: row.protocol,
subdomain: row.subdomain,
domainId: row.domainId,
enabled: row.enabled,
@@ -580,7 +578,7 @@ export async function getTraefikConfig(
continue;
}
const protocol = resource.protocol.toLowerCase();
const protocol = resource.mode === "udp" ? "udp" : "tcp"; // all of the other ones are tcp
const port = resource.proxyPort;
if (!port) {

View File

@@ -32,3 +32,4 @@ export * from "./verifySiteResourceAccess";
export * from "./logActionAudit";
export * from "./verifyOlmAccess";
export * from "./verifyLimits";
export * from "./verifyResourcePolicyAccess";

View File

@@ -16,3 +16,4 @@ export * from "./verifyApiKeyClientAccess";
export * from "./verifyApiKeySiteResourceAccess";
export * from "./verifyApiKeyIdpAccess";
export * from "./verifyApiKeyDomainAccess";
export * from "./verifyApiKeyResourcePolicyAccess";

View File

@@ -4,6 +4,7 @@ import { resourceAccessToken, resources, apiKeyOrg } from "@server/db";
import { and, eq } from "drizzle-orm";
import createHttpError from "http-errors";
import HttpCode from "@server/types/HttpCode";
import { getFirstString } from "@server/lib/requestParams";
export async function verifyApiKeyAccessTokenAccess(
req: Request,
@@ -12,7 +13,7 @@ export async function verifyApiKeyAccessTokenAccess(
) {
try {
const apiKey = req.apiKey;
const accessTokenId = req.params.accessTokenId;
const accessTokenId = getFirstString(req.params.accessTokenId);
if (!apiKey) {
return next(
@@ -20,6 +21,12 @@ export async function verifyApiKeyAccessTokenAccess(
);
}
if (!accessTokenId) {
return next(
createHttpError(HttpCode.BAD_REQUEST, "Invalid access token ID")
);
}
const [accessToken] = await db
.select()
.from(resourceAccessToken)

View File

@@ -4,6 +4,7 @@ import { apiKeys, apiKeyOrg } from "@server/db";
import { and, eq, or } from "drizzle-orm";
import createHttpError from "http-errors";
import HttpCode from "@server/types/HttpCode";
import { getFirstString } from "@server/lib/requestParams";
export async function verifyApiKeyApiKeyAccess(
req: Request,
@@ -14,8 +15,10 @@ export async function verifyApiKeyApiKeyAccess(
const { apiKey: callerApiKey } = req;
const apiKeyId =
req.params.apiKeyId || req.body.apiKeyId || req.query.apiKeyId;
const orgId = req.params.orgId;
getFirstString(req.params.apiKeyId) ||
getFirstString(req.body.apiKeyId) ||
getFirstString(req.query.apiKeyId);
const orgId = getFirstString(req.params.orgId);
if (!callerApiKey) {
return next(

View File

@@ -3,6 +3,7 @@ import { db, domains, orgDomains, apiKeyOrg } from "@server/db";
import { and, eq } from "drizzle-orm";
import createHttpError from "http-errors";
import HttpCode from "@server/types/HttpCode";
import { getFirstString } from "@server/lib/requestParams";
export async function verifyApiKeyDomainAccess(
req: Request,
@@ -12,8 +13,10 @@ export async function verifyApiKeyDomainAccess(
try {
const apiKey = req.apiKey;
const domainId =
req.params.domainId || req.body.domainId || req.query.domainId;
const orgId = req.params.orgId;
getFirstString(req.params.domainId) ||
getFirstString(req.body.domainId) ||
getFirstString(req.query.domainId);
const orgId = getFirstString(req.params.orgId);
if (!apiKey) {
return next(
@@ -27,6 +30,12 @@ export async function verifyApiKeyDomainAccess(
);
}
if (!orgId) {
return next(
createHttpError(HttpCode.BAD_REQUEST, "Invalid organization ID")
);
}
if (apiKey.isRoot) {
// Root keys can access any domain in any org
return next();

View File

@@ -4,6 +4,7 @@ import { idp, idpOrg, apiKeyOrg } from "@server/db";
import { and, eq } from "drizzle-orm";
import createHttpError from "http-errors";
import HttpCode from "@server/types/HttpCode";
import { getFirstString } from "@server/lib/requestParams";
export async function verifyApiKeyIdpAccess(
req: Request,
@@ -12,8 +13,12 @@ export async function verifyApiKeyIdpAccess(
) {
try {
const apiKey = req.apiKey;
const idpId = req.params.idpId || req.body.idpId || req.query.idpId;
const orgId = req.params.orgId;
const idpIdRaw =
getFirstString(req.params.idpId) ||
getFirstString(req.body.idpId) ||
getFirstString(req.query.idpId);
const idpId = Number.parseInt(idpIdRaw ?? "", 10);
const orgId = getFirstString(req.params.orgId);
if (!apiKey) {
return next(
@@ -27,7 +32,7 @@ export async function verifyApiKeyIdpAccess(
);
}
if (!idpId) {
if (Number.isNaN(idpId)) {
return next(
createHttpError(HttpCode.BAD_REQUEST, "Invalid IDP ID")
);

View File

@@ -4,6 +4,7 @@ import { apiKeyOrg } from "@server/db";
import { and, eq } from "drizzle-orm";
import createHttpError from "http-errors";
import HttpCode from "@server/types/HttpCode";
import { getFirstString } from "@server/lib/requestParams";
export async function verifyApiKeyOrgAccess(
req: Request,
@@ -12,7 +13,7 @@ export async function verifyApiKeyOrgAccess(
) {
try {
const apiKeyId = req.apiKey?.apiKeyId;
const orgId = req.params.orgId;
const orgId = getFirstString(req.params.orgId);
if (!apiKeyId) {
return next(
@@ -45,7 +46,7 @@ export async function verifyApiKeyOrgAccess(
}
if (!req.apiKeyOrg) {
next(
return next(
createHttpError(
HttpCode.FORBIDDEN,
"Key does not have access to this organization"

View File

@@ -0,0 +1,92 @@
import { Request, Response, NextFunction } from "express";
import { db } from "@server/db";
import { resourcePolicies, apiKeyOrg } from "@server/db";
import { eq, and } from "drizzle-orm";
import createHttpError from "http-errors";
import HttpCode from "@server/types/HttpCode";
export async function verifyApiKeyResourcePolicyAccess(
req: Request,
res: Response,
next: NextFunction
) {
const apiKey = req.apiKey;
const resourcePolicyId =
req.params.resourcePolicyId ||
req.body.resourcePolicyId ||
req.query.resourcePolicyId;
if (!apiKey) {
return next(
createHttpError(HttpCode.UNAUTHORIZED, "Key not authenticated")
);
}
try {
// Retrieve the resource policy
const [policy] = await db
.select()
.from(resourcePolicies)
.where(eq(resourcePolicies.resourcePolicyId, resourcePolicyId))
.limit(1);
if (!policy) {
return next(
createHttpError(
HttpCode.NOT_FOUND,
`Resource policy with ID ${resourcePolicyId} not found`
)
);
}
if (apiKey.isRoot) {
// Root keys can access any resource policy in any org
return next();
}
if (!policy.orgId) {
return next(
createHttpError(
HttpCode.INTERNAL_SERVER_ERROR,
`Resource policy with ID ${resourcePolicyId} does not have an organization ID`
)
);
}
// Verify that the API key is linked to the resource policy's organization
if (!req.apiKeyOrg) {
const apiKeyOrgResult = await db
.select()
.from(apiKeyOrg)
.where(
and(
eq(apiKeyOrg.apiKeyId, apiKey.apiKeyId),
eq(apiKeyOrg.orgId, policy.orgId)
)
)
.limit(1);
if (apiKeyOrgResult.length > 0) {
req.apiKeyOrg = apiKeyOrgResult[0];
}
}
if (!req.apiKeyOrg) {
return next(
createHttpError(
HttpCode.FORBIDDEN,
"Key does not have access to this organization"
)
);
}
return next();
} catch (error) {
return next(
createHttpError(
HttpCode.INTERNAL_SERVER_ERROR,
"Error verifying resource policy access"
)
);
}
}

View File

@@ -4,6 +4,7 @@ import { siteResources, apiKeyOrg } from "@server/db";
import { and, eq } from "drizzle-orm";
import createHttpError from "http-errors";
import HttpCode from "@server/types/HttpCode";
import { getFirstString } from "@server/lib/requestParams";
export async function verifyApiKeySiteResourceAccess(
req: Request,
@@ -12,7 +13,8 @@ export async function verifyApiKeySiteResourceAccess(
) {
try {
const apiKey = req.apiKey;
const siteResourceId = parseInt(req.params.siteResourceId);
const siteResourceIdRaw = getFirstString(req.params.siteResourceId);
const siteResourceId = Number.parseInt(siteResourceIdRaw ?? "", 10);
if (!apiKey) {
return next(
@@ -20,7 +22,7 @@ export async function verifyApiKeySiteResourceAccess(
);
}
if (!siteResourceId) {
if (Number.isNaN(siteResourceId)) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,

View File

@@ -4,6 +4,7 @@ import { resources, targets, apiKeyOrg } from "@server/db";
import { and, eq } from "drizzle-orm";
import createHttpError from "http-errors";
import HttpCode from "@server/types/HttpCode";
import { getFirstString } from "@server/lib/requestParams";
export async function verifyApiKeyTargetAccess(
req: Request,
@@ -12,7 +13,8 @@ export async function verifyApiKeyTargetAccess(
) {
try {
const apiKey = req.apiKey;
const targetId = parseInt(req.params.targetId);
const targetIdRaw = getFirstString(req.params.targetId);
const targetId = Number.parseInt(targetIdRaw ?? "", 10);
if (!apiKey) {
return next(
@@ -20,7 +22,7 @@ export async function verifyApiKeyTargetAccess(
);
}
if (isNaN(targetId)) {
if (Number.isNaN(targetId)) {
return next(
createHttpError(HttpCode.BAD_REQUEST, "Invalid target ID")
);

View File

@@ -7,6 +7,7 @@ import HttpCode from "@server/types/HttpCode";
import { canUserAccessResource } from "@server/auth/canUserAccessResource";
import { checkOrgAccessPolicy } from "#dynamic/lib/checkOrgAccessPolicy";
import { getUserOrgRoleIds } from "@server/lib/userOrgRoles";
import { getFirstString } from "@server/lib/requestParams";
export async function verifyAccessTokenAccess(
req: Request,
@@ -14,7 +15,7 @@ export async function verifyAccessTokenAccess(
next: NextFunction
) {
const userId = req.user!.userId;
const accessTokenId = req.params.accessTokenId;
const accessTokenId = getFirstString(req.params.accessTokenId);
if (!userId) {
return next(
@@ -22,6 +23,12 @@ export async function verifyAccessTokenAccess(
);
}
if (!accessTokenId) {
return next(
createHttpError(HttpCode.BAD_REQUEST, "Invalid access token ID")
);
}
const [accessToken] = await db
.select()
.from(resourceAccessToken)
@@ -87,7 +94,7 @@ export async function verifyAccessTokenAccess(
}
if (!req.userOrg) {
next(
return next(
createHttpError(
HttpCode.FORBIDDEN,
"User does not have access to this organization"

View File

@@ -6,6 +6,7 @@ import createHttpError from "http-errors";
import HttpCode from "@server/types/HttpCode";
import { checkOrgAccessPolicy } from "#dynamic/lib/checkOrgAccessPolicy";
import { getUserOrgRoleIds } from "@server/lib/userOrgRoles";
import { getFirstString } from "@server/lib/requestParams";
export async function verifyApiKeyAccess(
req: Request,
@@ -14,9 +15,24 @@ export async function verifyApiKeyAccess(
) {
try {
const userId = req.user!.userId;
const apiKeyId =
req.params.apiKeyId || req.body.apiKeyId || req.query.apiKeyId;
const orgId = req.params.orgId;
const apiKeyIdFromParams = getFirstString(req.params?.apiKeyId);
const apiKeyIdFromBody = getFirstString(req.body?.apiKeyId);
if (
apiKeyIdFromParams &&
apiKeyIdFromBody &&
apiKeyIdFromParams !== apiKeyIdFromBody
) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
"API key ID provided in both URL and body with different values"
)
);
}
const apiKeyId = apiKeyIdFromParams || apiKeyIdFromBody;
const orgId = getFirstString(req.params.orgId);
if (!userId) {
return next(
@@ -104,10 +120,7 @@ export async function verifyApiKeyAccess(
}
}
req.userOrgRoleIds = await getUserOrgRoleIds(
req.userOrg.userId,
orgId
);
req.userOrgRoleIds = await getUserOrgRoleIds(req.userOrg.userId, orgId);
return next();
} catch (error) {

View File

@@ -6,6 +6,7 @@ import createHttpError from "http-errors";
import HttpCode from "@server/types/HttpCode";
import { checkOrgAccessPolicy } from "#dynamic/lib/checkOrgAccessPolicy";
import { getUserOrgRoleIds } from "@server/lib/userOrgRoles";
import { getFirstString } from "@server/lib/requestParams";
export async function verifyDomainAccess(
req: Request,
@@ -14,9 +15,8 @@ export async function verifyDomainAccess(
) {
try {
const userId = req.user!.userId;
const domainId =
req.params.domainId;
const orgId = req.params.orgId;
const domainId = getFirstString(req.params.domainId);
const orgId = getFirstString(req.params.orgId);
if (!userId) {
return next(
@@ -62,10 +62,7 @@ export async function verifyDomainAccess(
.select()
.from(userOrgs)
.where(
and(
eq(userOrgs.userId, userId),
eq(userOrgs.orgId, orgId)
)
and(eq(userOrgs.userId, userId), eq(userOrgs.orgId, orgId))
)
.limit(1);
req.userOrg = userOrgRole[0];

View File

@@ -3,6 +3,7 @@ import createHttpError from "http-errors";
import HttpCode from "@server/types/HttpCode";
import { usageService } from "@server/lib/billing/usageService";
import { build } from "@server/build";
import { getFirstString } from "@server/lib/requestParams";
export async function verifyLimits(
req: Request,
@@ -13,7 +14,10 @@ export async function verifyLimits(
return next();
}
const orgId = req.userOrgId || req.apiKeyOrg?.orgId || req.params.orgId;
const orgId =
req.userOrgId ||
req.apiKeyOrg?.orgId ||
getFirstString(req.params.orgId);
if (!orgId) {
return next(); // its fine if we silently fail here because this is not critical to operation or security and its better user experience if we dont fail

View File

@@ -6,6 +6,7 @@ import createHttpError from "http-errors";
import HttpCode from "@server/types/HttpCode";
import { checkOrgAccessPolicy } from "#dynamic/lib/checkOrgAccessPolicy";
import { getUserOrgRoleIds } from "@server/lib/userOrgRoles";
import { getFirstString } from "@server/lib/requestParams";
export async function verifyOrgAccess(
req: Request,
@@ -13,7 +14,7 @@ export async function verifyOrgAccess(
next: NextFunction
) {
const userId = req.user!.userId;
const orgId = req.params.orgId;
const orgId = getFirstString(req.params.orgId);
if (!userId) {
return next(

View File

@@ -0,0 +1,127 @@
import { Request, Response, NextFunction } from "express";
import { db } from "@server/db";
import { resourcePolicies, 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 { getUserOrgRoleIds } from "@server/lib/userOrgRoles";
export async function verifyResourcePolicyAccess(
req: Request,
res: Response,
next: NextFunction
) {
const userId = req.user!.userId;
const resourcePolicyIdStr =
req.params?.resourcePolicyId ||
req.body?.resourcePolicyId ||
req.query?.resourcePolicyId;
const niceId = req.params?.niceId || req.body?.niceId || req.query?.niceId;
const orgId = req.params?.orgId || req.body?.orgId || req.query?.orgId;
try {
if (!userId) {
return next(
createHttpError(HttpCode.UNAUTHORIZED, "User not authenticated")
);
}
let policy: typeof resourcePolicies.$inferSelect | null = null;
if (orgId && niceId) {
const [policyRes] = await db
.select()
.from(resourcePolicies)
.where(
and(
eq(resourcePolicies.niceId, niceId),
eq(resourcePolicies.orgId, orgId)
)
)
.limit(1);
policy = policyRes ?? null;
} else {
const resourcePolicyId = parseInt(resourcePolicyIdStr);
if (isNaN(resourcePolicyId)) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
"Invalid resource policy ID"
)
);
}
const [policyRes] = await db
.select()
.from(resourcePolicies)
.where(eq(resourcePolicies.resourcePolicyId, resourcePolicyId))
.limit(1);
policy = policyRes ?? null;
}
if (!policy) {
return next(
createHttpError(
HttpCode.NOT_FOUND,
`Resource policy with ID ${resourcePolicyIdStr ?? niceId} not found`
)
);
}
if (!req.userOrg) {
const userOrgRes = await db
.select()
.from(userOrgs)
.where(
and(
eq(userOrgs.userId, userId),
eq(userOrgs.orgId, policy.orgId)
)
)
.limit(1);
req.userOrg = userOrgRes[0];
}
if (!req.userOrg || req.userOrg.orgId !== policy.orgId) {
return next(
createHttpError(
HttpCode.FORBIDDEN,
"User does not have access to this organization"
)
);
}
if (req.orgPolicyAllowed === undefined && req.userOrg.orgId) {
const policyCheck = await checkOrgAccessPolicy({
orgId: req.userOrg.orgId,
userId,
session: req.session
});
req.orgPolicyAllowed = policyCheck.allowed;
if (!policyCheck.allowed || policyCheck.error) {
return next(
createHttpError(
HttpCode.FORBIDDEN,
"Failed organization access policy check: " +
(policyCheck.error || "Unknown error")
)
);
}
}
req.userOrgRoleIds = await getUserOrgRoleIds(
req.userOrg.userId,
policy.orgId
);
req.userOrgId = policy.orgId;
return next();
} catch (error) {
return next(
createHttpError(
HttpCode.INTERNAL_SERVER_ERROR,
"Error verifying resource policy access"
)
);
}
}

View File

@@ -1,10 +1,16 @@
import { Request, Response, NextFunction } from "express";
import { db, userOrgs, siteProvisioningKeys, siteProvisioningKeyOrg } from "@server/db";
import {
db,
userOrgs,
siteProvisioningKeys,
siteProvisioningKeyOrg
} 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 { getUserOrgRoleIds } from "@server/lib/userOrgRoles";
import { getFirstString } from "@server/lib/requestParams";
export async function verifySiteProvisioningKeyAccess(
req: Request,
@@ -13,8 +19,10 @@ export async function verifySiteProvisioningKeyAccess(
) {
try {
const userId = req.user!.userId;
const siteProvisioningKeyId = req.params.siteProvisioningKeyId;
const orgId = req.params.orgId;
const siteProvisioningKeyId = getFirstString(
req.params.siteProvisioningKeyId
);
const orgId = getFirstString(req.params.orgId);
if (!userId) {
return next(
@@ -80,10 +88,7 @@ export async function verifySiteProvisioningKeyAccess(
.where(
and(
eq(userOrgs.userId, userId),
eq(
userOrgs.orgId,
row.siteProvisioningKeyOrg.orgId
)
eq(userOrgs.orgId, row.siteProvisioningKeyOrg.orgId)
)
)
.limit(1);

View File

@@ -7,6 +7,7 @@ import HttpCode from "@server/types/HttpCode";
import { canUserAccessResource } from "../auth/canUserAccessResource";
import { checkOrgAccessPolicy } from "#dynamic/lib/checkOrgAccessPolicy";
import { getUserOrgRoleIds } from "@server/lib/userOrgRoles";
import { getFirstString } from "@server/lib/requestParams";
export async function verifyTargetAccess(
req: Request,
@@ -14,7 +15,8 @@ export async function verifyTargetAccess(
next: NextFunction
) {
const userId = req.user!.userId;
const targetId = parseInt(req.params.targetId);
const targetIdRaw = getFirstString(req.params.targetId);
const targetId = Number.parseInt(targetIdRaw ?? "", 10);
if (!userId) {
return next(

View File

@@ -38,7 +38,7 @@ export function verifyUserCanSetUserOrgRoles() {
return next(
createHttpError(
HttpCode.FORBIDDEN,
"User does not have permission perform this action"
"User does not have permission to set user organization roles"
)
);
} catch (error) {

View File

@@ -4,6 +4,7 @@ import { userOrgs } from "@server/db";
import { and, eq } from "drizzle-orm";
import createHttpError from "http-errors";
import HttpCode from "@server/types/HttpCode";
import { getFirstString } from "@server/lib/requestParams";
export async function verifyUserIsOrgOwner(
req: Request,
@@ -11,7 +12,7 @@ export async function verifyUserIsOrgOwner(
next: NextFunction
) {
const userId = req.user!.userId;
const orgId = req.params.orgId;
const orgId = getFirstString(req.params.orgId);
if (!userId) {
return next(

View File

@@ -7,6 +7,7 @@ export enum OpenAPITags {
Org = "Organization",
PublicResource = "Public Resource",
PrivateResource = "Private Resource",
Policy = "Policy",
Role = "Role",
User = "User",
Invitation = "User Invitation",

View File

@@ -780,9 +780,9 @@ async function syncAcmeCerts(acmeJsonPath: string): Promise<void> {
}
}
logger.debug(
`acmeCertSync: cert for ${mainDomain} covers ${allDomains.size} domain(s): ${[...allDomains].join(", ")}`
);
// logger.debug(
// `acmeCertSync: cert for ${mainDomain} covers ${allDomains.size} domain(s): ${[...allDomains].join(", ")}`
// );
for (const domain of allDomains) {
try {

View File

@@ -13,7 +13,7 @@
import NodeCache from "node-cache";
import logger from "@server/logger";
import { redisManager } from "@server/private/lib/redis";
import { redisManager, regionalRedisManager } from "@server/private/lib/redis";
// Create local cache with maxKeys limit to prevent memory leaks
// With ~10k requests/day and 5min TTL, 10k keys should be more than sufficient
@@ -298,3 +298,147 @@ class AdaptiveCache {
// Export singleton instance
export const cache = new AdaptiveCache();
export default cache;
/**
* Regional adaptive cache backed by the in-cluster Redis instance.
* Falls back to a local NodeCache when the regional Redis is unavailable.
* Use this for data that is regional in nature (e.g. status history) so
* reads are served from the same cluster the user is hitting.
*/
const regionalLocalCache = new NodeCache({
stdTTL: 3600,
checkperiod: 120,
maxKeys: 10000
});
class RegionalAdaptiveCache {
private useRedis(): boolean {
return (
regionalRedisManager.isRedisEnabled() &&
regionalRedisManager.getHealthStatus().isHealthy
);
}
async set(key: string, value: any, ttl?: number): Promise<boolean> {
const effectiveTtl = ttl === 0 ? undefined : ttl;
const redisTtl = ttl === 0 ? undefined : (ttl ?? 3600);
if (this.useRedis()) {
try {
const serialized = JSON.stringify(value);
const success = await regionalRedisManager.set(
key,
serialized,
redisTtl
);
if (success) {
logger.debug(`[regional] Set key in Redis: ${key}`);
return true;
}
} catch (error) {
logger.error(
`[regional] Redis set error for key ${key}:`,
error
);
}
}
const success = regionalLocalCache.set(key, value, effectiveTtl || 0);
if (success) logger.debug(`[regional] Set key in local cache: ${key}`);
return success;
}
async get<T = any>(key: string): Promise<T | undefined> {
if (this.useRedis()) {
try {
const value = await regionalRedisManager.get(key);
if (value !== null) {
logger.debug(`[regional] Cache hit in Redis: ${key}`);
return JSON.parse(value) as T;
}
logger.debug(`[regional] Cache miss in Redis: ${key}`);
return undefined;
} catch (error) {
logger.error(
`[regional] Redis get error for key ${key}:`,
error
);
}
}
const value = regionalLocalCache.get<T>(key);
if (value !== undefined) {
logger.debug(`[regional] Cache hit in local cache: ${key}`);
} else {
logger.debug(`[regional] Cache miss in local cache: ${key}`);
}
return value;
}
async del(key: string | string[]): Promise<number> {
const keys = Array.isArray(key) ? key : [key];
let deletedCount = 0;
if (this.useRedis()) {
try {
for (const k of keys) {
const success = await regionalRedisManager.del(k);
if (success) {
deletedCount++;
logger.debug(`[regional] Deleted key from Redis: ${k}`);
}
}
if (deletedCount === keys.length) return deletedCount;
deletedCount = 0;
} catch (error) {
logger.error(`[regional] Redis del error:`, error);
deletedCount = 0;
}
}
for (const k of keys) {
const count = regionalLocalCache.del(k);
if (count > 0) {
deletedCount++;
logger.debug(`[regional] Deleted key from local cache: ${k}`);
}
}
return deletedCount;
}
async has(key: string): Promise<boolean> {
if (this.useRedis()) {
try {
const value = await regionalRedisManager.get(key);
return value !== null;
} catch (error) {
logger.error(
`[regional] Redis has error for key ${key}:`,
error
);
}
}
return regionalLocalCache.has(key);
}
/**
* Returns keys matching the given prefix from whichever backend is active.
* Redis uses a KEYS scan; local cache filters in-memory keys.
*/
async keysWithPrefix(prefix: string): Promise<string[]> {
if (this.useRedis()) {
try {
return await regionalRedisManager.keys(`${prefix}*`);
} catch (error) {
logger.error(`[regional] Redis keys error:`, error);
}
}
return regionalLocalCache.keys().filter((k) => k.startsWith(prefix));
}
getCurrentBackend(): "redis" | "local" {
return this.useRedis() ? "redis" : "local";
}
}
export const regionalCache = new RegionalAdaptiveCache();

View File

@@ -36,8 +36,6 @@ export async function getValidCertificatesForDomains(
domains: Set<string>,
useCache: boolean = true
): Promise<Array<CertificateResult>> {
const finalResults: CertificateResult[] = [];
const domainsToQuery = new Set<string>();
@@ -49,7 +47,26 @@ export async function getValidCertificatesForDomains(
if (cachedCert) {
finalResults.push(cachedCert); // Valid cache hit
} else {
domainsToQuery.add(domain); // Cache miss or expired
// Also check for a wildcard cache entry covering this domain's parent
const parts = domain.split(".");
let wildcardHit = false;
if (parts.length > 1) {
const parentDomain = parts.slice(1).join(".");
const wildcardCacheKey = `cert:*.${parentDomain}`;
const cachedWildcard =
await cache.get<CertificateResult>(wildcardCacheKey);
if (cachedWildcard) {
// Re-stamp queriedDomain so callers see the originally requested domain
finalResults.push({
...cachedWildcard,
queriedDomain: domain
});
wildcardHit = true;
}
}
if (!wildcardHit) {
domainsToQuery.add(domain); // Cache miss or expired
}
}
}
} else {
@@ -59,7 +76,10 @@ export async function getValidCertificatesForDomains(
// 2. If all domains were resolved from the cache, return early
if (domainsToQuery.size === 0) {
const decryptedResults = decryptFinalResults(finalResults, config.getRawConfig().server.secret!);
const decryptedResults = decryptFinalResults(
finalResults,
config.getRawConfig().server.secret!
);
return decryptedResults;
}
@@ -78,7 +98,8 @@ export async function getValidCertificatesForDomains(
const parentDomainsArray = Array.from(parentDomainsToQuery);
// Build wildcard variants: for each parent domain "example.com", also query "*.example.com"
const wildcardPrefixedArray = build != "saas" ? parentDomainsArray.map((d) => `*.${d}`) : [];
const wildcardPrefixedArray =
build != "saas" ? parentDomainsArray.map((d) => `*.${d}`) : [];
// 4. Build and execute a single, efficient Drizzle query
// This query fetches all potential exact and wildcard matches in one database round-trip.
@@ -172,11 +193,24 @@ export async function getValidCertificatesForDomains(
if (useCache) {
const cacheKey = `cert:${domain}`;
await cache.set(cacheKey, resultCert, 180);
// Also cache wildcard certs under a pattern key so other subdomains
// can find them without a DB round-trip
if (resultCert.wildcard) {
const normalizedCertDomain = normalizeWildcardDomain(
resultCert.domain
);
const wildcardCacheKey = `cert:*.${normalizedCertDomain}`;
await cache.set(wildcardCacheKey, resultCert, 180);
}
}
}
}
const decryptedResults = decryptFinalResults(finalResults, config.getRawConfig().server.secret!);
const decryptedResults = decryptFinalResults(
finalResults,
config.getRawConfig().server.secret!
);
return decryptedResults;
}

View File

@@ -24,7 +24,8 @@ import { LogStreamingManager } from "./LogStreamingManager";
*/
export const logStreamingManager = new LogStreamingManager();
if (build != "saas") { // this is handled separately in the saas build, so we don't want to start it here
if (build !== "saas") {
// this is handled separately in the saas build, so we don't want to start it here
logStreamingManager.start();
}

View File

@@ -73,6 +73,25 @@ export const privateConfigSchema = z
.object({
rejectUnauthorized: z.boolean().optional().default(true)
})
.optional(),
regional_redis: z
.object({
host: z.string(),
port: portSchema,
password: z
.string()
.optional()
.transform(getEnvOrYaml("REGIONAL_REDIS_PASSWORD")),
db: z.int().nonnegative().optional().default(0),
tls: z
.object({
rejectUnauthorized: z
.boolean()
.optional()
.default(true)
})
.optional()
})
.optional()
})
.optional(),

View File

@@ -109,14 +109,14 @@ class RedisManager {
password: redisConfig.password,
db: redisConfig.db
};
// Enable TLS if configured (required for AWS ElastiCache in-transit encryption)
if (redisConfig.tls) {
opts.tls = {
rejectUnauthorized: redisConfig.tls.rejectUnauthorized ?? true
};
}
return opts;
}
@@ -135,14 +135,14 @@ class RedisManager {
password: replica.password,
db: replica.db || redisConfig.db
};
// Enable TLS if configured (required for AWS ElastiCache in-transit encryption)
if (redisConfig.tls) {
opts.tls = {
rejectUnauthorized: redisConfig.tls.rejectUnauthorized ?? true
};
}
return opts;
}
@@ -855,3 +855,163 @@ class RedisManager {
export const redisManager = new RedisManager();
export const redis = redisManager.getClient();
export default redisManager;
/**
* Lightweight Redis manager for the regional (in-cluster) Redis instance.
* Connects only when `redis.regional_redis` is present in the private config
* and `flags.enable_redis` is true. No pub/sub — designed for low-latency
* caching of regionally-scoped data.
*/
class RegionalRedisManager {
private writeClient: Redis | null = null;
private readClient: Redis | null = null;
private isEnabled: boolean = false;
private isHealthy: boolean = false;
private connectionTimeout: number = 5000;
private commandTimeout: number = 5000;
constructor() {
if (build === "oss") return;
const cfg = privateConfig.getRawPrivateConfig();
if (!cfg.flags.enable_redis || !cfg.redis?.regional_redis) return;
this.isEnabled = true;
this.initializeClients();
}
private getConfig(): RedisOptions {
const r = privateConfig.getRawPrivateConfig().redis!.regional_redis!;
const opts: RedisOptions = {
host: r.host,
port: r.port,
password: r.password,
db: r.db
};
if (r.tls) {
opts.tls = { rejectUnauthorized: r.tls.rejectUnauthorized ?? true };
}
return opts;
}
private initializeClients(): void {
const cfg = this.getConfig();
const baseOpts = {
...cfg,
enableReadyCheck: false,
maxRetriesPerRequest: 3,
keepAlive: 10000,
connectTimeout: this.connectionTimeout,
commandTimeout: this.commandTimeout
};
try {
this.writeClient = new Redis(baseOpts);
// redis-1 (replica) handles reads; fall back to primary if not resolvable
this.readClient = new Redis({
...baseOpts,
host: cfg.host!.replace(/^(.*?)(\.\S+)$/, (_, h, rest) => {
// Derive replica hostname from the headless service pattern:
// redis.redis.svc.cluster.local -> redis-1.redis-headless.redis.svc.cluster.local
// If it doesn't look like a k8s service, just use the same host
return h + rest;
})
});
// For simplicity use same host for both; callers can always read from primary
// The real replica routing is handled by the StatefulSet headless service
this.readClient = this.writeClient;
this.writeClient.on("ready", () => {
logger.info("Regional Redis client ready");
this.isHealthy = true;
});
this.writeClient.on("error", (err) => {
logger.error("Regional Redis client error:", err);
this.isHealthy = false;
});
this.writeClient.on("reconnecting", () => {
logger.info("Regional Redis client reconnecting...");
this.isHealthy = false;
});
logger.info("Regional Redis client initialized");
} catch (error) {
logger.error("Failed to initialize regional Redis client:", error);
this.isEnabled = false;
}
}
public isRedisEnabled(): boolean {
return this.isEnabled && this.writeClient !== null && this.isHealthy;
}
public getHealthStatus() {
return { isEnabled: this.isEnabled, isHealthy: this.isHealthy };
}
public async set(
key: string,
value: string,
ttl?: number
): Promise<boolean> {
if (!this.isRedisEnabled() || !this.writeClient) return false;
try {
if (ttl) {
await this.writeClient.setex(key, ttl, value);
} else {
await this.writeClient.set(key, value);
}
return true;
} catch (error) {
logger.error("Regional Redis SET error:", error);
return false;
}
}
public async get(key: string): Promise<string | null> {
if (!this.isRedisEnabled() || !this.readClient) return null;
try {
return await this.readClient.get(key);
} catch (error) {
logger.error("Regional Redis GET error:", error);
return null;
}
}
public async del(key: string): Promise<boolean> {
if (!this.isRedisEnabled() || !this.writeClient) return false;
try {
await this.writeClient.del(key);
return true;
} catch (error) {
logger.error("Regional Redis DEL error:", error);
return false;
}
}
public async keys(pattern: string): Promise<string[]> {
if (!this.isRedisEnabled() || !this.readClient) return [];
try {
return await this.readClient.keys(pattern);
} catch (error) {
logger.error("Regional Redis KEYS error:", error);
return [];
}
}
public async disconnect(): Promise<void> {
try {
if (this.writeClient) {
await this.writeClient.quit();
this.writeClient = null;
}
this.readClient = null;
logger.info("Regional Redis client disconnected");
} catch (error) {
logger.error("Error disconnecting regional Redis client:", error);
}
}
}
export const regionalRedisManager = new RegionalRedisManager();

View File

@@ -12,6 +12,7 @@
*/
import {
browserGatewayTarget,
certificates,
db,
domainNamespaces,
@@ -95,9 +96,7 @@ export async function getTraefikConfig(
resourceName: resources.name,
fullDomain: resources.fullDomain,
ssl: resources.ssl,
http: resources.http,
proxyPort: resources.proxyPort,
protocol: resources.protocol,
subdomain: resources.subdomain,
domainId: resources.domainId,
enabled: resources.enabled,
@@ -109,6 +108,7 @@ export async function getTraefikConfig(
proxyProtocol: resources.proxyProtocol,
proxyProtocolVersion: resources.proxyProtocolVersion,
wildcard: resources.wildcard,
mode: resources.mode,
maintenanceModeEnabled: resources.maintenanceModeEnabled,
maintenanceModeType: resources.maintenanceModeType,
@@ -171,8 +171,8 @@ export async function getTraefikConfig(
),
inArray(sites.type, siteTypes),
allowRawResources
? isNotNull(resources.http) // ignore the http check if allow_raw_resources is true
: eq(resources.http, true)
? inArray(resources.mode, ["http", "udp", "tcp"]) // allow all three
: eq(resources.mode, "http")
)
)
.orderBy(desc(targets.priority), targets.targetId); // stable ordering
@@ -226,9 +226,8 @@ export async function getTraefikConfig(
key: key,
fullDomain: row.fullDomain,
ssl: row.ssl,
http: row.http,
proxyPort: row.proxyPort,
protocol: row.protocol,
mode: row.mode,
subdomain: row.subdomain,
domainId: row.domainId,
enabled: row.enabled,
@@ -277,10 +276,119 @@ export async function getTraefikConfig(
});
});
// Query browser gateway targets for this exit node
const browserGatewayRows = await db
.select({
// Resource fields
resourceId: resources.resourceId,
resourceName: resources.name,
fullDomain: resources.fullDomain,
ssl: resources.ssl,
subdomain: resources.subdomain,
domainId: resources.domainId,
enabled: resources.enabled,
wildcard: resources.wildcard,
domainCertResolver: domains.certResolver,
preferWildcardCert: domains.preferWildcardCert,
domainNamespaceId: domainNamespaces.domainNamespaceId,
// Browser gateway target fields
browserGatewayTargetId: browserGatewayTarget.browserGatewayTargetId,
bgType: browserGatewayTarget.type,
// Site fields
siteId: sites.siteId,
siteType: sites.type,
siteOnline: sites.online,
subnet: sites.subnet,
siteExitNodeId: sites.exitNodeId
})
.from(browserGatewayTarget)
.innerJoin(sites, eq(sites.siteId, browserGatewayTarget.siteId))
.innerJoin(
resources,
eq(resources.resourceId, browserGatewayTarget.resourceId)
)
.leftJoin(domains, eq(domains.domainId, resources.domainId))
.leftJoin(
domainNamespaces,
eq(domainNamespaces.domainId, resources.domainId)
)
.where(
and(
eq(resources.enabled, true),
or(
eq(sites.exitNodeId, exitNodeId),
and(
isNull(sites.exitNodeId),
sql`(${siteTypes.includes("local") ? 1 : 0} = 1)`,
eq(sites.type, "local"),
sql`(${build != "saas" ? 1 : 0} = 1)`
)
),
inArray(sites.type, siteTypes)
)
);
// Group browser gateway targets by resource
type BrowserGatewayResourceEntry = {
resourceId: number;
name: string;
fullDomain: string | null;
ssl: boolean | null;
subdomain: string | null;
domainId: string | null;
enabled: boolean | null;
wildcard: boolean | null;
domainCertResolver: string | null;
preferWildcardCert: boolean | null;
targets: {
browserGatewayTargetId: number;
bgType: string;
siteId: number;
siteType: string;
siteOnline: boolean | null;
subnet: string | null;
siteExitNodeId: number | null;
}[];
};
const browserGatewayResourcesMap = new Map<
number,
BrowserGatewayResourceEntry
>();
for (const row of browserGatewayRows) {
if (filterOutNamespaceDomains && row.domainNamespaceId) {
continue;
}
if (!browserGatewayResourcesMap.has(row.resourceId)) {
browserGatewayResourcesMap.set(row.resourceId, {
resourceId: row.resourceId,
name: sanitize(row.resourceName) || "",
fullDomain: row.fullDomain,
ssl: row.ssl,
subdomain: row.subdomain,
domainId: row.domainId,
enabled: row.enabled,
wildcard: row.wildcard,
domainCertResolver: row.domainCertResolver,
preferWildcardCert: row.preferWildcardCert,
targets: []
});
}
browserGatewayResourcesMap.get(row.resourceId)!.targets.push({
browserGatewayTargetId: row.browserGatewayTargetId,
bgType: row.bgType,
siteId: row.siteId,
siteType: row.siteType,
siteOnline: row.siteOnline,
subnet: row.subnet,
siteExitNodeId: row.siteExitNodeId
});
}
let siteResourcesWithFullDomain: {
siteResourceId: number;
fullDomain: string | null;
mode: "http" | "host" | "cidr";
mode: "http" | "host" | "cidr" | "ssh";
}[] = [];
if (build == "enterprise") {
// we dont want to do this on the cloud
@@ -324,6 +432,12 @@ export async function getTraefikConfig(
domains.add(sr.fullDomain);
}
}
// Include browser gateway resource domains
for (const bgResource of browserGatewayResourcesMap.values()) {
if (bgResource.enabled && bgResource.ssl && bgResource.fullDomain) {
domains.add(bgResource.fullDomain);
}
}
// get the valid certs for these domains
validCerts = await getValidCertificatesForDomains(domains, true); // we are caching here because this is called often
// logger.debug(`Valid certs for domains: ${JSON.stringify(validCerts)}`);
@@ -589,7 +703,7 @@ export async function getTraefikConfig(
resource.ssl ? entrypointHttps : entrypointHttp
],
service: maintenanceServiceName,
rule: `${rule} && (PathPrefix(\`/_next\`) || PathRegexp(\`^/__nextjs*\`))`,
rule: `${rule} && (PathPrefix(\`/_next\`) || PathRegexp(\`^/__nextjs*\`) || Path(\`/favicon.ico\`)) `,
priority: 2001,
...(resource.ssl ? { tls } : {})
};
@@ -830,7 +944,7 @@ export async function getTraefikConfig(
continue;
}
const protocol = resource.protocol.toLowerCase();
const protocol = resource.mode == "udp" ? "udp" : "tcp";
const port = resource.proxyPort;
if (!port) {
@@ -925,6 +1039,185 @@ export async function getTraefikConfig(
}
}
// Generate Traefik config for browser gateway resources
const browserGatewayPort = 39999;
for (const [, bgResource] of browserGatewayResourcesMap.entries()) {
if (!bgResource.enabled) continue;
if (!bgResource.domainId) continue;
if (!bgResource.fullDomain) continue;
if (!config_output.http.routers) config_output.http.routers = {};
if (!config_output.http.services) config_output.http.services = {};
const fullDomain = bgResource.fullDomain;
const additionalMiddlewares =
config.getRawConfig().traefik.additional_middlewares || [];
const routerMiddlewares = [
badgerMiddlewareName,
...additionalMiddlewares
];
const hostRule = `Host(\`${fullDomain}\`)`;
// Build TLS config
let tls = {};
if (!privateConfig.getRawPrivateConfig().flags.use_pangolin_dns) {
const domainParts = fullDomain.split(".");
let wildCard: string;
if (domainParts.length <= 2) {
wildCard = `*.${domainParts.join(".")}`;
} else {
wildCard = `*.${domainParts.slice(1).join(".")}`;
}
if (!bgResource.subdomain) {
wildCard = fullDomain;
}
const globalDefaultResolver =
config.getRawConfig().traefik.cert_resolver;
const globalDefaultPreferWildcard =
config.getRawConfig().traefik.prefer_wildcard_cert;
const resolverName = bgResource.domainCertResolver
? bgResource.domainCertResolver.trim()
: globalDefaultResolver;
const preferWildcard =
bgResource.preferWildcardCert !== undefined &&
bgResource.preferWildcardCert !== null
? bgResource.preferWildcardCert
: globalDefaultPreferWildcard;
tls = {
certResolver: resolverName,
...(preferWildcard ? { domains: [{ main: wildCard }] } : {})
};
} else {
const matchingCert = validCerts.find(
(cert) => cert.queriedDomain === fullDomain
);
if (!matchingCert) {
logger.debug(
`No matching certificate found for browser gateway domain: ${fullDomain}`
);
continue;
}
}
const bgUiServiceName = `bg-r${bgResource.resourceId}-ui-service`;
if (bgResource.ssl) {
const redirectRouterName = `bg-r${bgResource.resourceId}-redirect`;
config_output.http.routers![redirectRouterName] = {
entryPoints: [config.getRawConfig().traefik.http_entrypoint],
middlewares: [redirectHttpsMiddlewareName],
service: bgUiServiceName,
rule: hostRule,
priority: 100
};
}
// Collect online sites for this resource (for any type)
const anySiteOnline = bgResource.targets.some((t) => t.siteOnline);
// Group targets by type and generate per-type websocket routers and services
const typeMap = new Map<string, typeof bgResource.targets>();
for (const t of bgResource.targets) {
if (!typeMap.has(t.bgType)) typeMap.set(t.bgType, []);
typeMap.get(t.bgType)!.push(t);
}
for (const [bgType, typedTargets] of typeMap.entries()) {
const bgKey = `bg-r${bgResource.resourceId}-${bgType}`;
const bgRouterName = `${bgKey}-router`;
const bgServiceName = `${bgKey}-service`;
const bgRule = `${hostRule} && PathPrefix(\`/gateway/${bgType}\`)`;
const servers = typedTargets
.filter((t) => {
if (!t.siteOnline && anySiteOnline) return false;
if (t.siteType === "newt") return !!t.subnet;
return false; // browser gateway only supported on newt sites
})
.map((t) => ({
url: `http://${t.subnet!.split("/")[0]}:${browserGatewayPort}`
}))
.filter((v, i, a) => a.findIndex((u) => u.url === v.url) === i);
config_output.http.routers![bgRouterName] = {
entryPoints: [
bgResource.ssl
? config.getRawConfig().traefik.https_entrypoint
: config.getRawConfig().traefik.http_entrypoint
],
middlewares: routerMiddlewares,
service: bgServiceName,
rule: bgRule,
priority: 110, // highest - websocket path takes precedence
...(bgResource.ssl ? { tls } : {})
};
config_output.http.services![bgServiceName] = {
loadBalancer: {
servers
}
};
}
// UI: serve the browser gateway page from the internal pangolin instance.
// The primary type is used for the path rewrite (e.g. /rdp), mirroring
// how the maintenance page rewrites everything to /maintenance-screen.
const primaryType = typeMap.keys().next().value as string;
const internalHost = config.getRawConfig().server.internal_hostname;
const internalPort = config.getRawConfig().server.next_port;
const uiRewriteMiddlewareName = `bg-r${bgResource.resourceId}-ui-rewrite`;
const entrypoint = bgResource.ssl
? config.getRawConfig().traefik.https_entrypoint
: config.getRawConfig().traefik.http_entrypoint;
if (!config_output.http.middlewares) {
config_output.http.middlewares = {};
}
config_output.http.middlewares![uiRewriteMiddlewareName] = {
replacePathRegex: {
regex: "^/(.*)",
replacement: `/${primaryType}`
}
};
config_output.http.services![bgUiServiceName] = {
loadBalancer: {
servers: [
{
url: `http://${internalHost}:${internalPort}`
}
]
}
};
// Assets router at higher priority so /_next files load without rewrite
config_output.http.routers![
`bg-r${bgResource.resourceId}-assets-router`
] = {
entryPoints: [entrypoint],
middlewares: routerMiddlewares,
service: bgUiServiceName,
rule: `${hostRule} && (PathPrefix(\`/_next\`) || PathRegexp(\`^/__nextjs*\`) || Path(\`/favicon.ico\`))`,
priority: 101,
...(bgResource.ssl ? { tls } : {})
};
// Catch-all router rewrites everything on the domain to /{primaryType}
config_output.http.routers![`bg-r${bgResource.resourceId}-ui-router`] =
{
entryPoints: [entrypoint],
middlewares: [...routerMiddlewares, uiRewriteMiddlewareName],
service: bgUiServiceName,
rule: hostRule,
priority: 100,
...(bgResource.ssl ? { tls } : {})
};
}
// Add Traefik routes for siteResource aliases (HTTP mode + SSL) so that
// Traefik generates TLS certificates for those domains even when no
// matching resource exists yet.
@@ -1040,7 +1333,7 @@ export async function getTraefikConfig(
config_output.http.routers[`${siteResourceRouterName}-assets`] = {
entryPoints: [config.getRawConfig().traefik.https_entrypoint],
service: siteResourceServiceName,
rule: `Host(\`${fullDomain}\`) && (PathPrefix(\`/_next\`) || PathRegexp(\`^/__nextjs*\`))`,
rule: `Host(\`${fullDomain}\`) && (PathPrefix(\`/_next\`) || PathRegexp(\`^/__nextjs*\`) || Path(\`/favicon.ico\`))`,
priority: 101,
tls
};
@@ -1143,7 +1436,7 @@ export async function getTraefikConfig(
config.getRawConfig().traefik.https_entrypoint
],
service: "landing-service",
rule: `Host(\`${fullDomain}\`) && (PathRegexp(\`^/auth/resource/[^/]+$\`) || PathRegexp(\`^/auth/idp/[0-9]+/oidc/callback\`) || PathPrefix(\`/_next\`) || Path(\`/auth/org\`) || PathRegexp(\`^/__nextjs*\`))`,
rule: `Host(\`${fullDomain}\`) && (PathRegexp(\`^/auth/resource/[^/]+$\`) || PathRegexp(\`^/auth/idp/[0-9]+/oidc/callback\`) || PathPrefix(\`/_next\`) || Path(\`/auth/org\`) || PathRegexp(\`^/__nextjs*\`) || Path(\`/favicon.ico\`))`,
priority: 203,
tls: tls
};

View File

@@ -19,6 +19,7 @@ import { eq, and } from "drizzle-orm";
import createHttpError from "http-errors";
import HttpCode from "@server/types/HttpCode";
import logger from "@server/logger";
import { getFirstString } from "@server/lib/requestParams";
export async function verifyCertificateAccess(
req: Request,
@@ -27,11 +28,43 @@ export async function verifyCertificateAccess(
) {
try {
// Assume user/org access is already verified
const orgId = req.params.orgId;
const certId =
req.params.certId || req.body?.certId || req.query?.certId;
let domainId =
req.params.domainId || req.body?.domainId || req.query?.domainId;
const orgId = getFirstString(req.params.orgId);
const certIdFromParams = getFirstString(req.params?.certId);
const certIdFromBody = getFirstString(req.body?.certId);
if (
certIdFromParams &&
certIdFromBody &&
certIdFromParams !== certIdFromBody
) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
"Certificate ID provided in both URL and body with different values"
)
);
}
const certId = certIdFromParams || certIdFromBody;
const domainIdFromParams = getFirstString(req.params?.domainId);
const domainIdFromBody = getFirstString(req.body?.domainId);
if (
domainIdFromParams &&
domainIdFromBody &&
domainIdFromParams !== domainIdFromBody
) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
"Domain ID provided in both URL and body with different values"
)
);
}
let domainId = domainIdFromParams || domainIdFromBody;
if (!orgId) {
return next(
@@ -65,7 +98,7 @@ export async function verifyCertificateAccess(
);
}
domainId = cert.domainId;
domainId = cert.domainId ?? undefined;
if (!domainId) {
return next(
createHttpError(

View File

@@ -17,6 +17,7 @@ import { and, eq } from "drizzle-orm";
import createHttpError from "http-errors";
import HttpCode from "@server/types/HttpCode";
import { getUserOrgRoleIds } from "@server/lib/userOrgRoles";
import { getFirstString } from "@server/lib/requestParams";
export async function verifyIdpAccess(
req: Request,
@@ -25,8 +26,12 @@ export async function verifyIdpAccess(
) {
try {
const userId = req.user!.userId;
const idpId = req.params.idpId || req.body.idpId || req.query.idpId;
const orgId = req.params.orgId;
const idpIdRaw =
getFirstString(req.params.idpId) ||
getFirstString(req.body?.idpId) ||
getFirstString(req.query?.idpId);
const idpId = Number.parseInt(idpIdRaw ?? "", 10);
const orgId = getFirstString(req.params.orgId);
if (!userId) {
return next(
@@ -40,7 +45,7 @@ export async function verifyIdpAccess(
);
}
if (!idpId) {
if (Number.isNaN(idpId)) {
return next(
createHttpError(HttpCode.BAD_REQUEST, "Invalid key ID")
);

View File

@@ -18,6 +18,7 @@ import { and, eq } from "drizzle-orm";
import createHttpError from "http-errors";
import HttpCode from "@server/types/HttpCode";
import { getUserOrgRoleIds } from "@server/lib/userOrgRoles";
import { getFirstString } from "@server/lib/requestParams";
export async function verifyRemoteExitNodeAccess(
req: Request,
@@ -25,11 +26,11 @@ export async function verifyRemoteExitNodeAccess(
next: NextFunction
) {
const userId = req.user!.userId; // Assuming you have user information in the request
const orgId = req.params.orgId;
const orgId = getFirstString(req.params.orgId);
const remoteExitNodeId =
req.params.remoteExitNodeId ||
req.body.remoteExitNodeId ||
req.query.remoteExitNodeId;
getFirstString(req.params.remoteExitNodeId) ||
getFirstString(req.body?.remoteExitNodeId) ||
getFirstString(req.query?.remoteExitNodeId);
if (!userId) {
return next(
@@ -37,6 +38,15 @@ export async function verifyRemoteExitNodeAccess(
);
}
if (!orgId || !remoteExitNodeId) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
"Invalid organization or remote exit node ID"
)
);
}
try {
const [remoteExitNode] = await db
.select()

View File

@@ -25,7 +25,7 @@ export function verifyValidSubscription(tiers: Tier[]) {
next: NextFunction
): Promise<any> {
try {
if (build != "saas") {
if (build !== "saas") {
return next();
}

View File

@@ -0,0 +1,187 @@
/*
* This file is part of a proprietary work.
*
* Copyright (c) 2025-2026 Fossorial, Inc.
* All rights reserved.
*
* This file is licensed under the Fossorial Commercial License.
* You may not use this file except in compliance with the License.
* Unauthorized use, copying, modification, or distribution is strictly prohibited.
*
* This file is not licensed under the AGPLv3.
*/
import { Request, Response, NextFunction } from "express";
import { z } from "zod";
import {
browserGatewayTarget,
BrowserGatewayTarget,
db,
newts,
resources,
sites
} from "@server/db";
import { eq, and } from "drizzle-orm";
import response from "@server/lib/response";
import HttpCode from "@server/types/HttpCode";
import createHttpError from "http-errors";
import logger from "@server/logger";
import { fromError } from "zod-validation-error";
import { OpenAPITags, registry } from "@server/openApi";
import { encrypt } from "@server/lib/crypto";
import config from "@server/lib/config";
import { sendBrowserGatewayTargets } from "@server/routers/newt/targets";
import { generateId } from "@server/auth/sessions/app";
const paramsSchema = z.strictObject({
orgId: z.string().nonempty(),
resourceId: z.string().transform(Number).pipe(z.number().int().positive())
});
const bodySchema = z.strictObject({
siteId: z.number().int().positive(),
type: z.enum(["ssh", "rdp", "vnc"]),
destination: z.string().nonempty(),
destinationPort: z.number().int().min(1).max(65535)
});
export type CreateBrowserGatewayTargetResponse = BrowserGatewayTarget;
registry.registerPath({
method: "put",
path: "/org/{orgId}/resource/{resourceId}/browser-gateway-target",
description: "Create a browser gateway target for a resource.",
tags: [OpenAPITags.Org],
request: {
params: paramsSchema,
body: {
content: {
"application/json": {
schema: bodySchema
}
}
}
},
responses: {}
});
export async function createBrowserGatewayTarget(
req: Request,
res: Response,
next: NextFunction
): Promise<any> {
try {
const parsedParams = paramsSchema.safeParse(req.params);
if (!parsedParams.success) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
fromError(parsedParams.error).toString()
)
);
}
const { orgId, resourceId } = parsedParams.data;
const parsedBody = bodySchema.safeParse(req.body);
if (!parsedBody.success) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
fromError(parsedBody.error).toString()
)
);
}
const { siteId, type, destination, destinationPort } = parsedBody.data;
const [resource] = await db
.select()
.from(resources)
.where(
and(
eq(resources.resourceId, resourceId),
eq(resources.orgId, orgId)
)
)
.limit(1);
if (!resource) {
return next(
createHttpError(
HttpCode.NOT_FOUND,
`Resource with ID ${resourceId} not found in organization ${orgId}`
)
);
}
const [site] = await db
.select()
.from(sites)
.where(and(eq(sites.siteId, siteId), eq(sites.orgId, orgId)))
.limit(1);
if (!site) {
return next(
createHttpError(
HttpCode.NOT_FOUND,
`Site with ID ${siteId} not found in organization ${orgId}`
)
);
}
const plainToken = generateId(48);
const encryptedToken = encrypt(
plainToken,
config.getRawConfig().server.secret!
);
const [record] = await db
.insert(browserGatewayTarget)
.values({
resourceId,
siteId,
type,
destination,
destinationPort,
authToken: encryptedToken
})
.returning();
if (site.type === "newt") {
const [newt] = await db
.select()
.from(newts)
.where(eq(newts.siteId, siteId))
.limit(1);
if (newt) {
await sendBrowserGatewayTargets(
newt.newtId,
[record],
newt.version
);
}
}
logger.info(
`Created browser gateway target ${record.browserGatewayTargetId} for resource ${resourceId}`
);
return response<CreateBrowserGatewayTargetResponse>(res, {
data: record,
success: true,
error: false,
message: "Browser gateway target created successfully",
status: HttpCode.CREATED
});
} catch (error) {
logger.error(error);
return next(
createHttpError(
HttpCode.INTERNAL_SERVER_ERROR,
"Failed to create browser gateway target"
)
);
}
}

View File

@@ -0,0 +1,130 @@
/*
* This file is part of a proprietary work.
*
* Copyright (c) 2025-2026 Fossorial, Inc.
* All rights reserved.
*
* This file is licensed under the Fossorial Commercial License.
* You may not use this file except in compliance with the License.
* Unauthorized use, copying, modification, or distribution is strictly prohibited.
*
* This file is not licensed under the AGPLv3.
*/
import { Request, Response, NextFunction } from "express";
import { z } from "zod";
import { browserGatewayTarget, db, newts, sites } from "@server/db";
import { eq, and } from "drizzle-orm";
import response from "@server/lib/response";
import HttpCode from "@server/types/HttpCode";
import createHttpError from "http-errors";
import logger from "@server/logger";
import { fromError } from "zod-validation-error";
import { OpenAPITags, registry } from "@server/openApi";
import { removeBrowserGatewayTarget } from "@server/routers/newt/targets";
const paramsSchema = z.strictObject({
orgId: z.string().nonempty(),
browserGatewayTargetId: z
.string()
.transform(Number)
.pipe(z.number().int().positive())
});
registry.registerPath({
method: "delete",
path: "/org/{orgId}/browser-gateway-target/{browserGatewayTargetId}",
description: "Delete a browser gateway target.",
tags: [OpenAPITags.Org],
request: {
params: paramsSchema
},
responses: {}
});
export async function deleteBrowserGatewayTarget(
req: Request,
res: Response,
next: NextFunction
): Promise<any> {
try {
const parsedParams = paramsSchema.safeParse(req.params);
if (!parsedParams.success) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
fromError(parsedParams.error).toString()
)
);
}
const { orgId, browserGatewayTargetId } = parsedParams.data;
const [existing] = await db
.select({ bgt: browserGatewayTarget, site: sites })
.from(browserGatewayTarget)
.innerJoin(sites, eq(sites.siteId, browserGatewayTarget.siteId))
.where(
and(
eq(
browserGatewayTarget.browserGatewayTargetId,
browserGatewayTargetId
),
eq(sites.orgId, orgId)
)
)
.limit(1);
if (!existing) {
return next(
createHttpError(
HttpCode.NOT_FOUND,
`Browser gateway target with ID ${browserGatewayTargetId} not found`
)
);
}
await db
.delete(browserGatewayTarget)
.where(
eq(
browserGatewayTarget.browserGatewayTargetId,
browserGatewayTargetId
)
);
if (existing.site.type === "newt") {
const [newt] = await db
.select()
.from(newts)
.where(eq(newts.siteId, existing.bgt.siteId))
.limit(1);
if (newt) {
await removeBrowserGatewayTarget(
newt.newtId,
browserGatewayTargetId,
newt.version
);
}
}
logger.info(`Deleted browser gateway target ${browserGatewayTargetId}`);
return response(res, {
data: null,
success: true,
error: false,
message: "Browser gateway target deleted successfully",
status: HttpCode.OK
});
} catch (error) {
logger.error(error);
return next(
createHttpError(
HttpCode.INTERNAL_SERVER_ERROR,
"Failed to delete browser gateway target"
)
);
}
}

View File

@@ -0,0 +1,109 @@
/*
* This file is part of a proprietary work.
*
* Copyright (c) 2025-2026 Fossorial, Inc.
* All rights reserved.
*
* This file is licensed under the Fossorial Commercial License.
* You may not use this file except in compliance with the License.
* Unauthorized use, copying, modification, or distribution is strictly prohibited.
*
* This file is not licensed under the AGPLv3.
*/
import { Request, Response, NextFunction } from "express";
import { z } from "zod";
import {
browserGatewayTarget,
BrowserGatewayTarget,
db,
sites
} from "@server/db";
import { eq, and } from "drizzle-orm";
import response from "@server/lib/response";
import HttpCode from "@server/types/HttpCode";
import createHttpError from "http-errors";
import logger from "@server/logger";
import { fromError } from "zod-validation-error";
import { OpenAPITags, registry } from "@server/openApi";
const paramsSchema = z.strictObject({
orgId: z.string().nonempty(),
browserGatewayTargetId: z
.string()
.transform(Number)
.pipe(z.number().int().positive())
});
export type GetBrowserGatewayTargetResponse = BrowserGatewayTarget;
registry.registerPath({
method: "get",
path: "/org/{orgId}/browser-gateway-target/{browserGatewayTargetId}",
description: "Get a browser gateway target.",
tags: [OpenAPITags.Org],
request: {
params: paramsSchema
},
responses: {}
});
export async function getBrowserGatewayTarget(
req: Request,
res: Response,
next: NextFunction
): Promise<any> {
try {
const parsedParams = paramsSchema.safeParse(req.params);
if (!parsedParams.success) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
fromError(parsedParams.error).toString()
)
);
}
const { orgId, browserGatewayTargetId } = parsedParams.data;
const [result] = await db
.select({ bgt: browserGatewayTarget })
.from(browserGatewayTarget)
.innerJoin(sites, eq(sites.siteId, browserGatewayTarget.siteId))
.where(
and(
eq(
browserGatewayTarget.browserGatewayTargetId,
browserGatewayTargetId
),
eq(sites.orgId, orgId)
)
)
.limit(1);
if (!result) {
return next(
createHttpError(
HttpCode.NOT_FOUND,
`Browser gateway target with ID ${browserGatewayTargetId} not found`
)
);
}
return response<GetBrowserGatewayTargetResponse>(res, {
data: result.bgt,
success: true,
error: false,
message: "Browser gateway target retrieved successfully",
status: HttpCode.OK
});
} catch (error) {
logger.error(error);
return next(
createHttpError(
HttpCode.INTERNAL_SERVER_ERROR,
"Failed to retrieve browser gateway target"
)
);
}
}

View File

@@ -0,0 +1,18 @@
/*
* This file is part of a proprietary work.
*
* Copyright (c) 2025-2026 Fossorial, Inc.
* All rights reserved.
*
* This file is licensed under the Fossorial Commercial License.
* You may not use this file except in compliance with the License.
* Unauthorized use, copying, modification, or distribution is strictly prohibited.
*
* This file is not licensed under the AGPLv3.
*/
export * from "./createBrowserGatewayTarget";
export * from "./updateBrowserGatewayTarget";
export * from "./deleteBrowserGatewayTarget";
export * from "./getBrowserGatewayTarget";
export * from "./listBrowserGatewayTargets";

View File

@@ -0,0 +1,148 @@
/*
* This file is part of a proprietary work.
*
* Copyright (c) 2025-2026 Fossorial, Inc.
* All rights reserved.
*
* This file is licensed under the Fossorial Commercial License.
* You may not use this file except in compliance with the License.
* Unauthorized use, copying, modification, or distribution is strictly prohibited.
*
* This file is not licensed under the AGPLv3.
*/
import { Request, Response, NextFunction } from "express";
import { z } from "zod";
import {
browserGatewayTarget,
BrowserGatewayTarget,
db,
resources,
sites
} from "@server/db";
import { eq, and } from "drizzle-orm";
import response from "@server/lib/response";
import HttpCode from "@server/types/HttpCode";
import createHttpError from "http-errors";
import logger from "@server/logger";
import { fromError } from "zod-validation-error";
import { OpenAPITags, registry } from "@server/openApi";
const paramsSchema = z.strictObject({
orgId: z.string().nonempty(),
resourceId: z.string().transform(Number).pipe(z.number().int().positive())
});
const querySchema = z.object({
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 type ListBrowserGatewayTargetsResponse = {
targets: BrowserGatewayTarget[];
total: number;
limit: number;
offset: number;
};
registry.registerPath({
method: "get",
path: "/org/{orgId}/resource/{resourceId}/browser-gateway-targets",
description: "List browser gateway targets for a resource.",
tags: [OpenAPITags.Org],
request: {
params: paramsSchema,
query: querySchema
},
responses: {}
});
export async function listBrowserGatewayTargets(
req: Request,
res: Response,
next: NextFunction
): Promise<any> {
try {
const parsedParams = paramsSchema.safeParse(req.params);
if (!parsedParams.success) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
fromError(parsedParams.error).toString()
)
);
}
const { orgId, resourceId } = parsedParams.data;
const parsedQuery = querySchema.safeParse(req.query);
if (!parsedQuery.success) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
fromError(parsedQuery.error).toString()
)
);
}
const { limit, offset } = parsedQuery.data;
const [resource] = await db
.select()
.from(resources)
.where(
and(
eq(resources.resourceId, resourceId),
eq(resources.orgId, orgId)
)
)
.limit(1);
if (!resource) {
return next(
createHttpError(
HttpCode.NOT_FOUND,
`Resource with ID ${resourceId} not found in organization ${orgId}`
)
);
}
const targets = await db
.select()
.from(browserGatewayTarget)
.where(eq(browserGatewayTarget.resourceId, resourceId))
.limit(limit)
.offset(offset);
return response<ListBrowserGatewayTargetsResponse>(res, {
data: {
targets: targets,
total: targets.length,
limit,
offset
},
success: true,
error: false,
message: "Browser gateway targets retrieved successfully",
status: HttpCode.OK
});
} catch (error) {
logger.error(error);
return next(
createHttpError(
HttpCode.INTERNAL_SERVER_ERROR,
"Failed to list browser gateway targets"
)
);
}
}

View File

@@ -0,0 +1,180 @@
/*
* This file is part of a proprietary work.
*
* Copyright (c) 2025-2026 Fossorial, Inc.
* All rights reserved.
*
* This file is licensed under the Fossorial Commercial License.
* You may not use this file except in compliance with the License.
* Unauthorized use, copying, modification, or distribution is strictly prohibited.
*
* This file is not licensed under the AGPLv3.
*/
import { Request, Response, NextFunction } from "express";
import { z } from "zod";
import {
browserGatewayTarget,
BrowserGatewayTarget,
db,
newts,
sites
} from "@server/db";
import { eq, and } from "drizzle-orm";
import response from "@server/lib/response";
import HttpCode from "@server/types/HttpCode";
import createHttpError from "http-errors";
import logger from "@server/logger";
import { fromError } from "zod-validation-error";
import { OpenAPITags, registry } from "@server/openApi";
import { sendBrowserGatewayTargets } from "@server/routers/newt/targets";
const paramsSchema = z.strictObject({
orgId: z.string().nonempty(),
browserGatewayTargetId: z
.string()
.transform(Number)
.pipe(z.number().int().positive())
});
const bodySchema = z.strictObject({
siteId: z.number().int().positive().optional(),
type: z.enum(["ssh", "rdp", "vnc"]).optional(),
destination: z.string().nonempty().optional(),
destinationPort: z.number().int().min(1).max(65535).optional()
});
export type UpdateBrowserGatewayTargetResponse = BrowserGatewayTarget;
registry.registerPath({
method: "post",
path: "/org/{orgId}/browser-gateway-target/{browserGatewayTargetId}",
description: "Update a browser gateway target.",
tags: [OpenAPITags.Org],
request: {
params: paramsSchema,
body: {
content: {
"application/json": {
schema: bodySchema
}
}
}
},
responses: {}
});
export async function updateBrowserGatewayTarget(
req: Request,
res: Response,
next: NextFunction
): Promise<any> {
try {
const parsedParams = paramsSchema.safeParse(req.params);
if (!parsedParams.success) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
fromError(parsedParams.error).toString()
)
);
}
const { orgId, browserGatewayTargetId } = parsedParams.data;
const parsedBody = bodySchema.safeParse(req.body);
if (!parsedBody.success) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
fromError(parsedBody.error).toString()
)
);
}
const { siteId, type, destination, destinationPort } = parsedBody.data;
const [existing] = await db
.select({ bgt: browserGatewayTarget, site: sites })
.from(browserGatewayTarget)
.innerJoin(sites, eq(sites.siteId, browserGatewayTarget.siteId))
.where(
and(
eq(
browserGatewayTarget.browserGatewayTargetId,
browserGatewayTargetId
),
eq(sites.orgId, orgId)
)
)
.limit(1);
if (!existing) {
return next(
createHttpError(
HttpCode.NOT_FOUND,
`Browser gateway target with ID ${browserGatewayTargetId} not found`
)
);
}
const updateValues: Partial<BrowserGatewayTarget> = {};
if (siteId !== undefined) updateValues.siteId = siteId;
if (type !== undefined) updateValues.type = type;
if (destination !== undefined) updateValues.destination = destination;
if (destinationPort !== undefined)
updateValues.destinationPort = destinationPort;
const [updated] = await db
.update(browserGatewayTarget)
.set(updateValues)
.where(
eq(
browserGatewayTarget.browserGatewayTargetId,
browserGatewayTargetId
)
)
.returning();
const targetSiteId = siteId ?? existing.bgt.siteId;
const [site] = await db
.select()
.from(sites)
.where(eq(sites.siteId, targetSiteId))
.limit(1);
if (site && site.type === "newt") {
const [newt] = await db
.select()
.from(newts)
.where(eq(newts.siteId, targetSiteId))
.limit(1);
if (newt) {
await sendBrowserGatewayTargets(
newt.newtId,
[updated],
newt.version
);
}
}
logger.info(`Updated browser gateway target ${browserGatewayTargetId}`);
return response<UpdateBrowserGatewayTargetResponse>(res, {
data: updated,
success: true,
error: false,
message: "Browser gateway target updated successfully",
status: HttpCode.OK
});
} catch (error) {
logger.error(error);
return next(
createHttpError(
HttpCode.INTERNAL_SERVER_ERROR,
"Failed to update browser gateway target"
)
);
}
}

View File

@@ -31,7 +31,11 @@ import * as siteProvisioning from "#private/routers/siteProvisioning";
import * as eventStreamingDestination from "#private/routers/eventStreamingDestination";
import * as alertRule from "#private/routers/alertRule";
import * as healthChecks from "#private/routers/healthChecks";
import * as browserGatewayTarget from "#private/routers/browserGatewayTarget";
import * as labels from "#private/routers/labels";
import * as client from "@server/routers/client";
import * as resource from "#private/routers/resource";
import * as policy from "#private/routers/policy";
import {
verifyOrgAccess,
@@ -45,7 +49,8 @@ import {
verifyUserCanSetUserOrgRoles,
verifySiteProvisioningKeyAccess,
verifyIsLoggedInUser,
verifyAdmin
verifyAdmin,
verifyResourcePolicyAccess
} from "@server/middlewares";
import { ActionsEnum } from "@server/auth/actions";
import {
@@ -383,6 +388,39 @@ authenticated.get(
approval.countApprovals
);
authenticated.delete(
"/resource-policy/:resourcePolicyId",
verifyResourcePolicyAccess,
verifyValidLicense,
verifyValidSubscription(tierMatrix.resourcePolicies),
verifyLimits,
verifyUserHasAction(ActionsEnum.deleteResourcePolicy),
logActionAudit(ActionsEnum.deleteResourcePolicy),
policy.deleteResourcePolicy
);
authenticated.get(
"/org/:orgId/resource-policies",
verifyValidLicense,
verifyValidSubscription(tierMatrix.resourcePolicies),
verifyOrgAccess,
verifyLimits,
verifyUserHasAction(ActionsEnum.listResourcePolicies),
logActionAudit(ActionsEnum.listResourcePolicies),
policy.listResourcePolicies
);
authenticated.post(
"/org/:orgId/resource-policy",
verifyValidLicense,
verifyValidSubscription(tierMatrix.resourcePolicies),
verifyOrgAccess,
verifyLimits,
verifyUserHasAction(ActionsEnum.createResourcePolicy),
logActionAudit(ActionsEnum.createResourcePolicy),
policy.createResourcePolicy
);
authenticated.put(
"/org/:orgId/approvals/:approvalId",
verifyValidLicense,
@@ -733,6 +771,59 @@ authenticated.get(
alertRule.getAlertRule
);
authenticated.get(
"/org/:orgId/labels",
verifyValidLicense,
verifyOrgAccess,
verifyValidSubscription(tierMatrix.labels),
verifyUserHasAction(ActionsEnum.listOrgLabels),
labels.listOrgLabels
);
authenticated.post(
"/org/:orgId/labels",
verifyValidLicense,
verifyOrgAccess,
verifyValidSubscription(tierMatrix.labels),
verifyUserHasAction(ActionsEnum.createOrgLabel),
labels.createOrgLabel
);
authenticated.patch(
"/org/:orgId/label/:labelId",
verifyValidLicense,
verifyOrgAccess,
verifyValidSubscription(tierMatrix.labels),
verifyUserHasAction(ActionsEnum.updateOrgLabel),
labels.updateOrgLabel
);
authenticated.delete(
"/org/:orgId/label/:labelId",
verifyValidLicense,
verifyOrgAccess,
verifyUserHasAction(ActionsEnum.deleteOrgLabel),
labels.deleteOrgLabel
);
authenticated.put(
"/org/:orgId/label/:labelId/attach",
verifyValidLicense,
verifyOrgAccess,
verifyValidSubscription(tierMatrix.labels),
verifyUserHasAction(ActionsEnum.attachLabelToItem),
labels.attachLabelToItem
);
authenticated.put(
"/org/:orgId/label/:labelId/detach",
verifyValidLicense,
verifyOrgAccess,
verifyValidSubscription(tierMatrix.labels),
verifyUserHasAction(ActionsEnum.detachLabelFromItem),
labels.detachLabelFromItem
);
authenticated.get(
"/org/:orgId/health-checks",
verifyValidLicense,
@@ -788,3 +879,48 @@ authenticated.post(
verifyClientAccess,
client.rebuildClientAssociationsCacheRoute
);
authenticated.put(
"/org/:orgId/resource/:resourceId/browser-gateway-target",
verifyValidLicense,
verifyOrgAccess,
verifyLimits,
verifyUserHasAction(ActionsEnum.createBrowserGatewayTarget),
logActionAudit(ActionsEnum.createBrowserGatewayTarget),
browserGatewayTarget.createBrowserGatewayTarget
);
authenticated.get(
"/org/:orgId/resource/:resourceId/browser-gateway-targets",
verifyValidLicense,
verifyOrgAccess,
verifyUserHasAction(ActionsEnum.listBrowserGatewayTargets),
browserGatewayTarget.listBrowserGatewayTargets
);
authenticated.get(
"/org/:orgId/browser-gateway-target/:browserGatewayTargetId",
verifyValidLicense,
verifyOrgAccess,
verifyUserHasAction(ActionsEnum.getBrowserGatewayTarget),
browserGatewayTarget.getBrowserGatewayTarget
);
authenticated.post(
"/org/:orgId/browser-gateway-target/:browserGatewayTargetId",
verifyValidLicense,
verifyOrgAccess,
verifyLimits,
verifyUserHasAction(ActionsEnum.updateBrowserGatewayTarget),
logActionAudit(ActionsEnum.updateBrowserGatewayTarget),
browserGatewayTarget.updateBrowserGatewayTarget
);
authenticated.delete(
"/org/:orgId/browser-gateway-target/:browserGatewayTargetId",
verifyValidLicense,
verifyOrgAccess,
verifyUserHasAction(ActionsEnum.deleteBrowserGatewayTarget),
logActionAudit(ActionsEnum.deleteBrowserGatewayTarget),
browserGatewayTarget.deleteBrowserGatewayTarget
);

View File

@@ -16,40 +16,44 @@ 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 { getFirstString } from "@server/lib/requestParams";
import privateConfig from "#private/lib/config";
import { GenerateNewLicenseResponse } from "@server/routers/generatedLicense/types";
export interface CreateNewLicenseResponse {
data: Data
success: boolean
error: boolean
message: string
status: number
data: Data;
success: boolean;
error: boolean;
message: string;
status: number;
}
export interface Data {
licenseKey: LicenseKey
licenseKey: LicenseKey;
}
export interface LicenseKey {
id: number
instanceName: any
instanceId: string
licenseKey: string
tier: string
type: string
quantity: number
quantity_2: number
isValid: boolean
updatedAt: string
createdAt: string
expiresAt: string
paidFor: boolean
orgId: string
metadata: string
id: number;
instanceName: any;
instanceId: string;
licenseKey: string;
tier: string;
type: string;
quantity: number;
quantity_2: number;
isValid: boolean;
updatedAt: string;
createdAt: string;
expiresAt: string;
paidFor: boolean;
orgId: string;
metadata: string;
}
export async function createNewLicense(orgId: string, licenseData: any): Promise<CreateNewLicenseResponse> {
export async function createNewLicense(
orgId: string,
licenseData: any
): Promise<CreateNewLicenseResponse> {
try {
const response = await fetch(
`${privateConfig.getRawPrivateConfig().server.fossorial_api}/api/v1/license-internal/enterprise/${orgId}/create`, // this says enterprise but it does both
@@ -80,7 +84,7 @@ export async function generateNewLicense(
next: NextFunction
): Promise<any> {
try {
const { orgId } = req.params;
const orgId = getFirstString(req.params.orgId);
if (!orgId) {
return next(

View File

@@ -16,6 +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 { getFirstString } from "@server/lib/requestParams";
import privateConfig from "#private/lib/config";
import {
GeneratedLicenseKey,
@@ -55,7 +56,7 @@ export async function listSaasLicenseKeys(
next: NextFunction
): Promise<any> {
try {
const { orgId } = req.params;
const orgId = getFirstString(req.params.orgId);
if (!orgId) {
return next(

View File

@@ -45,8 +45,11 @@ import {
users,
userOrgs,
roleResources,
rolePolicies,
userResources,
userPolicies,
resourceRules,
resourcePolicyRules,
userOrgRoles,
roles
} from "@server/db";
@@ -430,7 +433,10 @@ hybridRouter.get(
);
// Decrypt and save key file
const decryptedKey = decrypt(cert.keyFile!, config.getRawConfig().server.secret!);
const decryptedKey = decrypt(
cert.keyFile!,
config.getRawConfig().server.secret!
);
// Return only the certificate data without org information
return {
@@ -531,7 +537,10 @@ hybridRouter.get(
wildcardCandidates.length > 0
? and(
eq(resources.wildcard, true),
inArray(resources.fullDomain, wildcardCandidates)
inArray(
resources.fullDomain,
wildcardCandidates
)
)
: sql`false`
)
@@ -545,10 +554,10 @@ hybridRouter.get(
if (
result &&
await checkExitNodeOrg(
(await checkExitNodeOrg(
remoteExitNode.exitNodeId,
result.resources.orgId
)
))
) {
// If the exit node is not allowed for the org, return an error
return next(
@@ -1132,22 +1141,43 @@ hybridRouter.get(
);
}
const roleResourceAccess = await db
.select()
.from(roleResources)
.where(
and(
eq(roleResources.resourceId, resourceId),
eq(roleResources.roleId, roleId)
const [direct, viaPolicies] = await Promise.all([
db
.select()
.from(roleResources)
.where(
and(
eq(roleResources.resourceId, resourceId),
eq(roleResources.roleId, roleId)
)
)
)
.limit(1);
.limit(1),
db
.select({
roleId: rolePolicies.roleId,
resourcePolicyId: rolePolicies.resourcePolicyId
})
.from(rolePolicies)
.innerJoin(
resources,
eq(
resources.resourcePolicyId,
rolePolicies.resourcePolicyId
)
)
.where(
and(
eq(resources.resourceId, resourceId),
eq(rolePolicies.roleId, roleId)
)
)
.limit(1)
]);
const result =
roleResourceAccess.length > 0 ? roleResourceAccess[0] : null;
const result = direct[0] ?? viaPolicies[0] ?? null;
return response<typeof roleResources.$inferSelect | null>(res, {
data: result,
data: result as any,
success: true,
error: false,
message: result
@@ -1222,21 +1252,44 @@ hybridRouter.get(
);
}
const roleResourceAccess = await db
.select({
resourceId: roleResources.resourceId,
roleId: roleResources.roleId
})
.from(roleResources)
.where(
and(
eq(roleResources.resourceId, resourceId),
inArray(roleResources.roleId, roleIds)
)
);
const [direct, viaPolicies] = await Promise.all([
db
.select({
resourceId: roleResources.resourceId,
roleId: roleResources.roleId
})
.from(roleResources)
.where(
and(
eq(roleResources.resourceId, resourceId),
inArray(roleResources.roleId, roleIds)
)
),
roleIds.length > 0
? db
.select({
resourceId: sql<number>`${resourceId}`,
roleId: rolePolicies.roleId
})
.from(rolePolicies)
.innerJoin(
resources,
eq(
resources.resourcePolicyId,
rolePolicies.resourcePolicyId
)
)
.where(
and(
eq(resources.resourceId, resourceId),
inArray(rolePolicies.roleId, roleIds)
)
)
: Promise.resolve([])
]);
const result =
roleResourceAccess.length > 0 ? roleResourceAccess : null;
const combined = [...direct, ...viaPolicies];
const result = combined.length > 0 ? combined : null;
return response<{ resourceId: number; roleId: number }[] | null>(
res,
@@ -1397,10 +1450,45 @@ hybridRouter.get(
);
}
const rules = await db
.select()
.from(resourceRules)
.where(eq(resourceRules.resourceId, resourceId));
const [directRules, policyRules] = await Promise.all([
db
.select()
.from(resourceRules)
.where(eq(resourceRules.resourceId, resourceId)),
db
.select({
ruleId: resourcePolicyRules.ruleId,
resourceId: sql<number>`${resourceId}`,
enabled: resourcePolicyRules.enabled,
priority: resourcePolicyRules.priority,
action: resourcePolicyRules.action,
match: resourcePolicyRules.match,
value: resourcePolicyRules.value
})
.from(resourcePolicyRules)
.innerJoin(
resources,
eq(
resources.resourcePolicyId,
resourcePolicyRules.resourcePolicyId
)
)
.where(eq(resources.resourceId, resourceId))
]);
const maxDirectPriority = directRules.reduce(
(max, r) => Math.max(max, r.priority),
0
);
const offsetPolicyRules = policyRules.map((r) => ({
...r,
priority: maxDirectPriority + r.priority
}));
const rules = [
...directRules,
...offsetPolicyRules
] as (typeof resourceRules.$inferSelect)[];
// backward compatibility: COUNTRY -> GEOIP
// TODO: remove this after a few versions once all exit nodes are updated

View File

@@ -16,6 +16,7 @@ import * as org from "#private/routers/org";
import * as logs from "#private/routers/auditLogs";
import * as alertEvents from "#private/routers/alertEvents";
import * as certificates from "#private/routers/certificates";
import * as browserGatewayTarget from "#private/routers/browserGatewayTarget";
import {
verifyApiKeyHasAction,
@@ -215,3 +216,43 @@ authenticated.delete(
logActionAudit(ActionsEnum.removeUserRole),
user.removeUserRole
);
authenticated.put(
"/org/:orgId/resource/:resourceId/browser-gateway-target",
verifyApiKeyOrgAccess,
verifyLimits,
verifyApiKeyHasAction(ActionsEnum.createBrowserGatewayTarget),
logActionAudit(ActionsEnum.createBrowserGatewayTarget),
browserGatewayTarget.createBrowserGatewayTarget
);
authenticated.get(
"/org/:orgId/resource/:resourceId/browser-gateway-targets",
verifyApiKeyOrgAccess,
verifyApiKeyHasAction(ActionsEnum.listBrowserGatewayTargets),
browserGatewayTarget.listBrowserGatewayTargets
);
authenticated.get(
"/org/:orgId/browser-gateway-target/:browserGatewayTargetId",
verifyApiKeyOrgAccess,
verifyApiKeyHasAction(ActionsEnum.getBrowserGatewayTarget),
browserGatewayTarget.getBrowserGatewayTarget
);
authenticated.post(
"/org/:orgId/browser-gateway-target/:browserGatewayTargetId",
verifyApiKeyOrgAccess,
verifyLimits,
verifyApiKeyHasAction(ActionsEnum.updateBrowserGatewayTarget),
logActionAudit(ActionsEnum.updateBrowserGatewayTarget),
browserGatewayTarget.updateBrowserGatewayTarget
);
authenticated.delete(
"/org/:orgId/browser-gateway-target/:browserGatewayTargetId",
verifyApiKeyOrgAccess,
verifyApiKeyHasAction(ActionsEnum.deleteBrowserGatewayTarget),
logActionAudit(ActionsEnum.deleteBrowserGatewayTarget),
browserGatewayTarget.deleteBrowserGatewayTarget
);

View File

@@ -0,0 +1,224 @@
/*
* This file is part of a proprietary work.
*
* Copyright (c) 2025-2026 Fossorial, Inc.
* All rights reserved.
*
* This file is licensed under the Fossorial Commercial License.
* You may not use this file except in compliance with the License.
* Unauthorized use, copying, modification, or distribution is strictly prohibited.
*
* This file is not licensed under the AGPLv3.
*/
import {
clients,
clientLabels,
db,
labels,
resourceLabels,
resources,
siteLabels,
siteResourceLabels,
siteResources,
sites
} from "@server/db";
import response from "@server/lib/response";
import logger from "@server/logger";
import HttpCode from "@server/types/HttpCode";
import { and, eq, isNull } from "drizzle-orm";
import { NextFunction, Request, Response } from "express";
import createHttpError from "http-errors";
import { z } from "zod";
import { fromError } from "zod-validation-error";
const paramsSchema = z.strictObject({
orgId: z.string().nonempty(),
labelId: z.string().transform(Number).pipe(z.int().positive())
});
const attachLabelBodySchema = z.strictObject({
siteId: z.number().int().optional(),
resourceId: z.number().int().optional(),
siteResourceId: z.number().int().optional(),
clientId: z.number().int().optional()
});
export async function attachLabelToItem(
req: Request,
res: Response,
next: NextFunction
) {
try {
const parsedParams = paramsSchema.safeParse(req.params);
if (!parsedParams.success) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
fromError(parsedParams.error).toString()
)
);
}
const { orgId, labelId } = parsedParams.data;
const parsedBody = attachLabelBodySchema.safeParse(req.body);
if (!parsedBody.success) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
fromError(parsedBody.error).toString()
)
);
}
const { siteId, resourceId, siteResourceId, clientId } =
parsedBody.data;
if (!siteId && !resourceId && !siteResourceId && !clientId) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
"At least one of `siteId`, `resourceId`, `siteResourceId` or `clientId` should be provided."
)
);
}
const [existing] = await db
.select()
.from(labels)
.where(and(eq(labels.labelId, labelId), eq(labels.orgId, orgId)));
if (!existing) {
return next(
createHttpError(
HttpCode.NOT_FOUND,
`Label with Id ${labelId} not found`
)
);
}
if (siteId) {
const siteCount = await db.$count(
sites,
and(eq(sites.siteId, siteId), eq(sites.orgId, orgId))
);
if (siteCount === 0) {
return next(
createHttpError(
HttpCode.NOT_FOUND,
`Site with Id ${siteId} doesn't exist.`
)
);
}
// idempotent, calling this endpoint multiple times should attach the label only once
await db
.insert(siteLabels)
.values({
labelId,
siteId
})
.onConflictDoNothing();
}
if (resourceId) {
const resourceCount = await db.$count(
resources,
and(
eq(resources.resourceId, resourceId),
eq(resources.orgId, orgId)
)
);
if (resourceCount === 0) {
return next(
createHttpError(
HttpCode.NOT_FOUND,
`Resource with Id ${resourceId} doesn't exist.`
)
);
}
// idempotent, calling this endpoint multiple times should attach the label only once
await db
.insert(resourceLabels)
.values({
labelId,
resourceId
})
.onConflictDoNothing();
}
if (siteResourceId) {
const resourceCount = await db.$count(
siteResources,
and(
eq(siteResources.siteResourceId, siteResourceId),
eq(siteResources.orgId, orgId)
)
);
if (resourceCount === 0) {
return next(
createHttpError(
HttpCode.NOT_FOUND,
`SiteResource with Id ${siteResourceId} doesn't exist.`
)
);
}
// idempotent, calling this endpoint multiple times should attach the label only once
await db
.insert(siteResourceLabels)
.values({
labelId,
siteResourceId
})
.onConflictDoNothing();
}
if (clientId) {
const clientCount = await db.$count(
clients,
and(
eq(clients.clientId, clientId),
eq(clients.orgId, orgId),
isNull(clients.userId)
)
);
if (clientCount === 0) {
return next(
createHttpError(
HttpCode.NOT_FOUND,
`Client with Id ${clientId} doesn't exist.`
)
);
}
// idempotent, calling this endpoint multiple times should attach the label only once
await db
.insert(clientLabels)
.values({
labelId,
clientId
})
.onConflictDoNothing();
}
return response(res, {
data: {},
success: true,
error: false,
message: "Label attached successfully",
status: HttpCode.OK
});
} catch (error) {
logger.error(error);
return next(
createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred")
);
}
}

View File

@@ -0,0 +1,149 @@
/*
* This file is part of a proprietary work.
*
* Copyright (c) 2025-2026 Fossorial, Inc.
* All rights reserved.
*
* This file is licensed under the Fossorial Commercial License.
* You may not use this file except in compliance with the License.
* Unauthorized use, copying, modification, or distribution is strictly prohibited.
*
* This file is not licensed under the AGPLv3.
*/
import {
db,
labels,
resourceLabels,
resources,
siteLabels,
sites
} from "@server/db";
import response from "@server/lib/response";
import logger from "@server/logger";
import type { CreateOrEditLabelResponse } from "@server/routers/labels/types";
import HttpCode from "@server/types/HttpCode";
import { and, eq } from "drizzle-orm";
import { NextFunction, Request, Response } from "express";
import createHttpError from "http-errors";
import { z } from "zod";
import { fromError } from "zod-validation-error";
const paramsSchema = z.strictObject({
orgId: z.string().nonempty()
});
const bodySchema = z.strictObject({
name: z.string().nonempty(),
color: z
.string()
.regex(/^#?([0-9a-f]{6}|[0-9a-f]{3})$/i)
.nonempty(),
siteId: z.number().int().optional(),
resourceId: z.number().int().optional()
});
export async function createOrgLabel(
req: Request,
res: Response,
next: NextFunction
) {
try {
const parsedParams = paramsSchema.safeParse(req.params);
if (!parsedParams.success) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
fromError(parsedParams.error).toString()
)
);
}
const { orgId } = parsedParams.data;
const parsedBody = bodySchema.safeParse(req.body);
if (!parsedBody.success) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
fromError(parsedBody.error).toString()
)
);
}
const { name, color, siteId, resourceId } = parsedBody.data;
if (siteId) {
const siteCount = await db.$count(
sites,
and(eq(sites.siteId, siteId), eq(sites.orgId, orgId))
);
if (siteCount === 0) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
`Site with Id ${siteId} doesn't exist.`
)
);
}
}
if (resourceId) {
const resourceCount = await db.$count(
resources,
and(
eq(resources.resourceId, resourceId),
eq(resources.orgId, orgId)
)
);
if (resourceCount === 0) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
`Resource with Id ${resourceId} doesn't exist.`
)
);
}
}
const label = await db.transaction(async (tx) => {
const [label] = await tx
.insert(labels)
.values({
name,
color,
orgId
})
.returning();
if (siteId) {
await tx.insert(siteLabels).values({
siteId,
labelId: label.labelId
});
}
if (resourceId) {
await tx.insert(resourceLabels).values({
resourceId,
labelId: label.labelId
});
}
return label;
});
return response<CreateOrEditLabelResponse>(res, {
data: { label },
success: true,
error: false,
message: "Org Label created successfully",
status: HttpCode.CREATED
});
} catch (error) {
logger.error(error);
return next(
createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred")
);
}
}

View File

@@ -0,0 +1,72 @@
/*
* This file is part of a proprietary work.
*
* Copyright (c) 2025-2026 Fossorial, Inc.
* All rights reserved.
*
* This file is licensed under the Fossorial Commercial License.
* You may not use this file except in compliance with the License.
* Unauthorized use, copying, modification, or distribution is strictly prohibited.
*
* This file is not licensed under the AGPLv3.
*/
import { db, labels } from "@server/db";
import response from "@server/lib/response";
import logger from "@server/logger";
import HttpCode from "@server/types/HttpCode";
import { and, eq } from "drizzle-orm";
import { NextFunction, Request, Response } from "express";
import createHttpError from "http-errors";
import { z } from "zod";
import { fromError } from "zod-validation-error";
const paramsSchema = z.strictObject({
orgId: z.string().nonempty(),
labelId: z.string().transform(Number).pipe(z.int().positive())
});
export async function deleteOrgLabel(
req: Request,
res: Response,
next: NextFunction
) {
try {
const parsedParams = paramsSchema.safeParse(req.params);
if (!parsedParams.success) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
fromError(parsedParams.error).toString()
)
);
}
const { orgId, labelId } = parsedParams.data;
const [existing] = await db
.select()
.from(labels)
.where(and(eq(labels.labelId, labelId), eq(labels.orgId, orgId)));
if (!existing) {
return next(createHttpError(HttpCode.NOT_FOUND, "Label not found"));
}
await db
.delete(labels)
.where(and(eq(labels.labelId, labelId), eq(labels.orgId, orgId)));
return response(res, {
data: null,
success: true,
error: false,
message: "Label deleted successfully",
status: HttpCode.OK
});
} catch (error) {
logger.error(error);
return next(
createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred")
);
}
}

View File

@@ -0,0 +1,224 @@
/*
* This file is part of a proprietary work.
*
* Copyright (c) 2025-2026 Fossorial, Inc.
* All rights reserved.
*
* This file is licensed under the Fossorial Commercial License.
* You may not use this file except in compliance with the License.
* Unauthorized use, copying, modification, or distribution is strictly prohibited.
*
* This file is not licensed under the AGPLv3.
*/
import {
clients,
clientLabels,
db,
labels,
resourceLabels,
resources,
siteLabels,
siteResourceLabels,
siteResources,
sites
} from "@server/db";
import response from "@server/lib/response";
import logger from "@server/logger";
import HttpCode from "@server/types/HttpCode";
import { and, eq, isNull } from "drizzle-orm";
import { NextFunction, Request, Response } from "express";
import createHttpError from "http-errors";
import { z } from "zod";
import { fromError } from "zod-validation-error";
const paramsSchema = z.strictObject({
orgId: z.string().nonempty(),
labelId: z.string().transform(Number).pipe(z.int().positive())
});
const detachLabelBodySchema = z.strictObject({
siteId: z.number().int().optional(),
resourceId: z.number().int().optional(),
siteResourceId: z.number().int().optional(),
clientId: z.number().int().optional()
});
export async function detachLabelFromItem(
req: Request,
res: Response,
next: NextFunction
) {
try {
const parsedParams = paramsSchema.safeParse(req.params);
if (!parsedParams.success) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
fromError(parsedParams.error).toString()
)
);
}
const { orgId, labelId } = parsedParams.data;
const parsedBody = detachLabelBodySchema.safeParse(req.body);
if (!parsedBody.success) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
fromError(parsedBody.error).toString()
)
);
}
const { siteId, resourceId, siteResourceId, clientId } =
parsedBody.data;
if (!siteId && !resourceId && !siteResourceId && !clientId) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
"At least one of `siteId`, `resourceId`, `siteResourceId` or `clientId` should be provided."
)
);
}
const [existing] = await db
.select()
.from(labels)
.where(and(eq(labels.labelId, labelId), eq(labels.orgId, orgId)));
if (!existing) {
return next(
createHttpError(
HttpCode.NOT_FOUND,
`Label with Id ${labelId} not found`
)
);
}
if (siteId) {
const siteCount = await db.$count(
sites,
and(eq(sites.siteId, siteId), eq(sites.orgId, orgId))
);
if (siteCount === 0) {
return next(
createHttpError(
HttpCode.NOT_FOUND,
`Site with Id ${siteId} doesn't exist.`
)
);
}
await db
.delete(siteLabels)
.where(
and(
eq(siteLabels.labelId, labelId),
eq(siteLabels.siteId, siteId)
)
);
}
if (resourceId) {
const resourceCount = await db.$count(
resources,
and(
eq(resources.resourceId, resourceId),
eq(resources.orgId, orgId)
)
);
if (resourceCount === 0) {
return next(
createHttpError(
HttpCode.NOT_FOUND,
`Resource with Id ${resourceId} doesn't exist.`
)
);
}
await db
.delete(resourceLabels)
.where(
and(
eq(resourceLabels.labelId, labelId),
eq(resourceLabels.resourceId, resourceId)
)
);
}
if (siteResourceId) {
const resourceCount = await db.$count(
siteResources,
and(
eq(siteResources.siteResourceId, siteResourceId),
eq(siteResources.orgId, orgId)
)
);
if (resourceCount === 0) {
return next(
createHttpError(
HttpCode.NOT_FOUND,
`SiteResource with Id ${siteResourceId} doesn't exist.`
)
);
}
await db
.delete(siteResourceLabels)
.where(
and(
eq(siteResourceLabels.labelId, labelId),
eq(siteResourceLabels.siteResourceId, siteResourceId)
)
);
}
if (clientId) {
const clientCount = await db.$count(
clients,
and(
eq(clients.clientId, clientId),
eq(clients.orgId, orgId),
isNull(clients.userId)
)
);
if (clientCount === 0) {
return next(
createHttpError(
HttpCode.NOT_FOUND,
`Client with Id ${clientId} doesn't exist.`
)
);
}
await db
.delete(clientLabels)
.where(
and(
eq(clientLabels.labelId, labelId),
eq(clientLabels.clientId, clientId)
)
);
}
return response(res, {
data: {},
success: true,
error: false,
message: "Label detached successfully",
status: HttpCode.OK
});
} catch (error) {
logger.error(error);
return next(
createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred")
);
}
}

View File

@@ -0,0 +1,19 @@
/*
* This file is part of a proprietary work.
*
* Copyright (c) 2025-2026 Fossorial, Inc.
* All rights reserved.
*
* This file is licensed under the Fossorial Commercial License.
* You may not use this file except in compliance with the License.
* Unauthorized use, copying, modification, or distribution is strictly prohibited.
*
* This file is not licensed under the AGPLv3.
*/
export * from "./listOrgLabels";
export * from "./createOrgLabel";
export * from "./updateOrgLabel";
export * from "./attachLabelToItem";
export * from "./detachLabelFromItem";
export * from "./deleteOrgLabel";

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