Compare commits

..

158 Commits

Author SHA1 Message Date
Owen Schwartz 6e271028f3 Merge pull request #3245 from fosrl/dev
Bugfixes
2026-06-11 16:17:41 -07:00
Owen 820f66e58f Properly hide things with disable enterprise flag 2026-06-11 16:10:29 -07:00
Owen b0fdc10e06 Properly hide things with disable enterprise flag 2026-06-11 16:01:32 -07:00
miloschwartz b82b41ed26 fix migration 2026-06-11 15:02:29 -07:00
miloschwartz 3e977ba00d make paid alert position more consistent on resource 2026-06-11 12:38:08 -07:00
Owen Schwartz a724b07846 Merge pull request #3244 from fosrl/dev
fix paywalling
2026-06-11 12:27:49 -07:00
Owen 5f0bc71bcd Merge branch 'main' into dev 2026-06-11 12:26:31 -07:00
miloschwartz aea7827c1a fix paywalling 2026-06-11 12:26:01 -07:00
Owen Schwartz d865c4c55b Merge pull request #3242 from fosrl/dev
Use ssh like mode host
2026-06-11 11:29:45 -07:00
Owen 5baf0c3c09 Use ssh like mode host 2026-06-11 11:11:50 -07:00
Owen Schwartz cfe33eb974 Merge pull request #3241 from fosrl/dev
dev
2026-06-10 21:47:44 -07:00
Owen 71273e1b1c Try to fix large query problem 2026-06-10 21:41:34 -07:00
Owen 02f6e2a8c3 Add ; fix lint 2026-06-10 20:56:26 -07:00
Owen Schwartz 3cc244a1d3 Merge pull request #3240 from fosrl/dev
Fix small bugs with paid features, ui, docs
2026-06-10 20:49:59 -07:00
Owen 1d9c4dd9e2 Fix padding 2026-06-10 20:46:53 -07:00
Owen b9dd0c8e43 Add advantech install link 2026-06-10 20:46:43 -07:00
Owen cd052976eb Properly paywall the edit policy screen 2026-06-10 20:38:59 -07:00
Owen cc498f0e33 Properly paywall ui for labels 2026-06-10 20:32:07 -07:00
Owen 1a942937e6 Remove precheck on websocket for now 2026-06-10 20:24:41 -07:00
Owen d81d1a6b7f Merge branch 'dev' of github.com:fosrl/pangolin into dev 2026-06-10 20:24:22 -07:00
Owen f64d04e827 Add loading back to create resource 2026-06-10 18:23:01 -07:00
miloschwartz 540aee3fe2 update docs links 2026-06-10 17:52:42 -07:00
Owen Schwartz 10542d7282 Merge pull request #3239 from fosrl/dev
1.19.0
2026-06-10 16:50:32 -07:00
Owen b1d52ad1a3 Update tiers 2026-06-10 16:27:25 -07:00
Owen ce2fbef805 Filter only newt sites in the browser gateway 2026-06-10 16:16:42 -07:00
Owen e312b31e02 Merge branch 'dev' of github.com:fosrl/pangolin into dev 2026-06-10 15:50:52 -07:00
Owen bc156c715d 24 hour requirement for updates 2026-06-10 15:50:43 -07:00
miloschwartz 9a4c1f23c6 support remove server admin 2026-06-10 15:10:58 -07:00
miloschwartz 6921447fab fix typo 2026-06-10 11:55:20 -07:00
Owen d47449b082 Add notes about inline policy to api endpoints 2026-06-10 10:24:31 -07:00
Owen 665806dfe8 Add some documentation; pull the override values 2026-06-10 10:03:16 -07:00
Owen e248571268 Merge branch 'dev' of github.com:fosrl/pangolin into dev 2026-06-09 22:02:24 -07:00
miloschwartz fcf03854ff fix tag input wrapping 2026-06-09 22:01:13 -07:00
Owen dd1fba4e45 Also clear the roles and users 2026-06-09 21:59:30 -07:00
miloschwartz a1ab8d8f35 standardize client titles 2026-06-09 21:47:15 -07:00
miloschwartz c789e967db standardize client titles 2026-06-09 21:36:54 -07:00
Owen d870b9ff49 Drop the not null on resource columns 2026-06-09 21:36:27 -07:00
miloschwartz 9c09019ddb add protocol filter 2026-06-09 21:33:56 -07:00
Owen 9d88683fc5 Reset resource info when on inline policy 2026-06-09 21:28:25 -07:00
miloschwartz dd2c9f2a02 check resource policy in verifyResourceAccess middleware 2026-06-09 17:52:31 -07:00
miloschwartz bdb38db5bc fix form responsiveness 2026-06-09 16:52:18 -07:00
Owen 96a54fc9cc Fix import issue in migrations 2026-06-09 16:51:55 -07:00
Owen 3a485f74f1 Move session migration out of the loop 2026-06-09 16:16:14 -07:00
Owen 92b0340324 Merge branch 'dev' of github.com:fosrl/pangolin into dev 2026-06-09 16:10:35 -07:00
miloschwartz 9257ac01c7 add learn more links to auto update 2026-06-09 16:08:07 -07:00
Owen 4d1d0d9fcb Add warning if we cant reach the vnc server 2026-06-09 16:02:52 -07:00
Owen f186e7e99e Dont allow asn or country without having maxmind 2026-06-09 16:02:52 -07:00
miloschwartz 1aa6e3511f dont show policy on tcp/udp resources 2026-06-09 15:56:24 -07:00
miloschwartz fb6f5b3953 form layout improvements 2026-06-09 15:42:28 -07:00
Owen c85a7f6ac5 Migrate unkown openapi response from string to {} 2026-06-09 15:35:08 -07:00
Owen dd54be523f Dont need to check user exists for the whitelist 2026-06-09 15:26:35 -07:00
Owen d57f064d4c Fix spelling 2026-06-09 15:26:35 -07:00
miloschwartz 34799b7de2 move maintenance page config to tab 2026-06-09 15:07:55 -07:00
miloschwartz 20a66bba6f fix resource context updating problem 2026-06-09 14:49:57 -07:00
miloschwartz cdb43d9658 dont set whitelist until click set button in dialog 2026-06-09 14:36:50 -07:00
miloschwartz 6581ccafa3 fix toggle on pin or passcode not working on policy form 2026-06-09 14:34:58 -07:00
miloschwartz a3a45b4239 add safe read 2026-06-09 14:09:36 -07:00
Owen d6634b6e8a Add types 2026-06-09 12:16:00 -07:00
Owen 1089cfbacc Update query to be more efficient 2026-06-09 11:54:46 -07:00
Owen 1907a3c93b Link to primary org only when you can see billing 2026-06-09 10:33:42 -07:00
miloschwartz 407ba567a0 various visual changes 2026-06-08 22:07:53 -07:00
Owen f28571629f Make sure the pamMode is push for host resources 2026-06-08 21:54:06 -07:00
Owen 5a575c916b Handle backward compatability 2026-06-08 21:11:57 -07:00
Owen 9a7e534b10 Ssh session closed card 2026-06-08 17:44:48 -07:00
Owen 42974d1739 Make sure the skip to idp is pulled 2026-06-08 17:41:59 -07:00
Owen 780e8babe4 Perfect toolbar 2026-06-08 17:39:07 -07:00
Owen 2c7b8006cf Add gray bar 2026-06-08 16:07:36 -07:00
Owen 35066c1388 Add pulldown toolbar 2026-06-08 16:00:38 -07:00
miloschwartz 135a5d38af make form grids more consistent 2026-06-08 16:00:30 -07:00
Owen 1b7c1ffa70 Set the target port from the resource 2026-06-08 15:39:26 -07:00
Owen 641f643d2d Prefil the port with the best guess port 2026-06-08 15:39:26 -07:00
Owen b4ecfceb5e Show more information about error 2026-06-08 15:39:25 -07:00
Owen 08a84d4bb1 Add some connection feedback 2026-06-08 15:39:25 -07:00
Owen 4dbad7ab24 Close the tab when exiting 2026-06-08 15:39:25 -07:00
miloschwartz 859c0c9477 add description text to share link path input 2026-06-08 15:33:12 -07:00
miloschwartz d294bf8534 support uploading csv or txt to sudo commands and groups 2026-06-08 15:30:03 -07:00
miloschwartz 3c8fea382f improve unix group and sudo commands inputs 2026-06-08 14:36:10 -07:00
Owen b81bfcfcee Fix type error 2026-06-08 12:21:43 -07:00
Milo Schwartz 56c415ca05 Merge pull request #3219 from Fredkiss3/refactor/standardize-clear-buttons
feat: make clear filter buttons more consistent accross tables
2026-06-08 12:07:55 -07:00
Owen 74fdcceace Reconnect newts when a exit node comes back online 2026-06-08 12:02:12 -07:00
Owen 7dec8ba998 Add exit node if the sites dont have one 2026-06-08 12:02:12 -07:00
miloschwartz c9dc6affe7 Merge branch 'dev' into resource-policies-restyle 2026-06-08 12:00:08 -07:00
miloschwartz 8fe45ba78c prevent duplicate label names 2026-06-08 11:59:15 -07:00
Fred KISSIE 934886caea Merge branch 'dev' into refactor/standardize-clear-buttons 2026-06-08 20:42:11 +02:00
miloschwartz fae258b145 add labels to user-resources query 2026-06-08 10:55:24 -07:00
miloschwartz 9f224f655f Merge branch 'resource-policies-restyle' into dev 2026-06-08 10:38:13 -07:00
miloschwartz aea7df7dc2 rename share links 2026-06-08 10:37:46 -07:00
miloschwartz 3b675f7de1 policies and policy on resource structure in a good place 2026-06-07 12:19:33 -07:00
Owen 8daf7c2872 Rename and add browser target update 2026-06-07 12:07:08 -07:00
Owen c394490473 Update browser targets 2026-06-07 10:43:16 -07:00
Owen 3b6b78b3e1 Update traefik config 2026-06-06 16:14:20 -07:00
miloschwartz aa47f522ef move toggle on general page 2026-06-06 15:34:34 -07:00
Owen 8658198a93 Remove unnessicary auth token 2026-06-06 13:47:23 -07:00
Owen 4b770d1385 Fix issues 2026-06-06 13:34:24 -07:00
miloschwartz cd4d7372a0 Merge branch 'resource-policies-restyle' into dev 2026-06-06 12:27:59 -07:00
Owen dc8243cb51 Fix form rendering issue 2026-06-06 12:27:14 -07:00
miloschwartz 7b1f8d98f3 make the rule rows draggable 2026-06-06 12:27:11 -07:00
miloschwartz dd8bcbb3e3 first pass restyle of auth methods and rules 2026-06-05 21:04:03 -07:00
Owen d1af7a153f Enforece some more things on the types 2026-06-05 16:57:53 -07:00
Owen 13efa47db7 Merge branch 'dev' of github.com:fosrl/pangolin into dev 2026-06-05 16:12:46 -07:00
Owen 69bd61c308 Update migrations 2026-06-05 16:02:28 -07:00
Owen 7b7ff51289 Add target mode and auth token 2026-06-05 15:37:21 -07:00
Owen 772ac8af73 Remove browser gateway targets for regular targets 2026-06-05 15:30:42 -07:00
miloschwartz 8ee520dbb5 form validation improvements 2026-06-05 14:55:27 -07:00
Owen 8e5d9e94a9 Fix delete site only working on newt site 2026-06-05 14:37:44 -07:00
Owen c9cb28af45 Rename to public-policies 2026-06-05 14:30:36 -07:00
Fred KISSIE a994f8ff07 💄 Column filter buttons for log tables 2026-06-05 21:47:08 +02:00
miloschwartz ea8eaf9736 adjust targets input styles 2026-06-05 12:43:00 -07:00
miloschwartz b78db3daef move policies routes 2026-06-05 12:43:00 -07:00
Owen 7cf3f8df92 Rename proxy -> public 2026-06-05 12:12:27 -07:00
Owen f2b5cff3f9 Fix resource protection status showing wrong 2026-06-05 12:12:27 -07:00
Owen 6de9ab8f05 Add watermark on missing login pages 2026-06-05 12:12:27 -07:00
Owen ad0e800d8d Fix validation error and bring alias back to table 2026-06-05 12:12:27 -07:00
miloschwartz 65470fb64b adjust styles 2026-06-05 12:04:33 -07:00
miloschwartz f23142336b enable editing resource policy niceID 2026-06-05 11:57:55 -07:00
miloschwartz 2da4987cd3 rename policies 2026-06-05 11:52:51 -07:00
miloschwartz 253ba554a2 fix resources cell styling 2026-06-05 11:46:30 -07:00
Fred KISSIE 95ce91d94b Merge branch 'dev' into refactor/standardize-clear-buttons 2026-06-05 20:21:34 +02:00
Fred KISSIE a4548fd874 💄 Break all text 2026-06-05 19:59:28 +02:00
Fred KISSIE eb03fb7060 ♻️ standardize http request log data-tables 2026-06-05 19:28:30 +02:00
miloschwartz add9b8dfb0 many minor visual improvements 2026-06-04 22:25:18 -07:00
Owen 2adb7b64cb Add the resource name 2026-06-04 22:10:38 -07:00
Owen 84fef5f1d6 Resource policy api backward compatability 2026-06-04 22:02:42 -07:00
Owen def1e9c851 Add region back to the rules 2026-06-04 21:28:29 -07:00
Owen 67b08ca61e Properly do disable enterprise features this time 2026-06-04 21:18:04 -07:00
Owen 614df75880 Add policy to blueprints 2026-06-04 21:18:04 -07:00
Owen 676cf37ee2 Make sure things are paywalled in the blueprints 2026-06-04 21:18:04 -07:00
Owen 6b96e3dce6 Make sure the disableEnterpriseFeatures works 2026-06-04 21:18:04 -07:00
miloschwartz b67037e2ea use default animation speed on dialog 2026-06-04 18:19:23 -07:00
miloschwartz 5a5b77cf62 update project cursor rules 2026-06-04 18:13:26 -07:00
miloschwartz d2793dfad7 use react forms 2026-06-04 18:07:50 -07:00
miloschwartz ff507f1275 use standard error alert 2026-06-04 17:44:45 -07:00
miloschwartz 6b04bcb383 translate strings in auth pages for ssh, vnc, and rdp 2026-06-04 17:35:43 -07:00
miloschwartz b2f1115ef8 standardize and fix branding on new resources auth pages 2026-06-04 17:24:06 -07:00
Owen 567ef23ac4 Add initial advantech install commands 2026-06-04 16:59:58 -07:00
Owen 6affebc666 Finish adding ssh toggle 2026-06-04 16:59:58 -07:00
miloschwartz 889f78ddb8 use resource name in ssh/rdp/vnc page meta 2026-06-04 16:45:35 -07:00
Owen 9d3f96cf83 Add disable_private_http_placeholder 2026-06-04 16:41:07 -07:00
miloschwartz e5d0673bbf prefill site field on create private resource when filtering sites 2026-06-04 16:30:14 -07:00
miloschwartz 0907c0346f remove check on oidc login 2026-06-04 16:24:29 -07:00
Owen 6420a90d08 Replace tab component 2026-06-04 16:21:30 -07:00
Owen Schwartz 7fa1180d10 Merge pull request #3221 from fosrl/dev
1.19.0-rc.1
2026-06-04 15:45:27 -07:00
Owen 769d36e289 Fix http resources not being pulled 2026-06-04 15:36:25 -07:00
Owen a7a41b820e Add missing sshAccess key 2026-06-04 15:20:52 -07:00
Fred KISSIE 33fdc9a94f 🚧 wip: column filter button 2026-06-04 21:04:15 +02:00
Owen Schwartz 8b50f1fb65 Merge pull request #3218 from fosrl/dev
Fix installer
2026-06-04 11:21:59 -07:00
Owen 2d78a4b628 Fix installer 2026-06-04 11:21:40 -07:00
Fred KISSIE c86026c941 ♻️ refactor 2026-06-04 20:09:07 +02:00
Fred KISSIE db014e3446 ♻️ use the same clear filter text for clearing filters in the column filter buttons 2026-06-04 20:08:27 +02:00
Fred KISSIE feb8045643 ♻️ refactor 2026-06-04 19:54:43 +02:00
Fred KISSIE d485a09318 ♻️ use site label filter column 2026-06-04 19:45:54 +02:00
Fred KISSIE 9cff5f66b1 🚧 wip: site label column filter standardized 2026-06-04 19:40:24 +02:00
Owen Schwartz 527d4cc777 Merge pull request #3215 from fosrl/dev
1.19.0-rc.0
2026-06-04 10:34:20 -07:00
Owen Schwartz 01361884eb Potential fix for pull request finding 'CodeQL / Insecure randomness'
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
2026-06-04 10:33:15 -07:00
Owen 6c4cbcab5d Fix eslint errors 2026-06-04 10:22:29 -07:00
Owen Schwartz aac25f0a53 Merge pull request #3214 from marcschaeferger/dev
Prevent cross-org site binding in target create/update
2026-06-04 10:11:53 -07:00
Marc Schäfer f617f93a94 test(middleware): add regression tests for cross-org site binding prevention
Test the org-match logic in verifySiteAccess:
- Same org: allowed
- Cross-org: rejected with 403
- No prior org context (site-only routes): check skipped, normal flow

Test route stack ordering:
- verifySiteAccess runs after verifyResourceAccess/verifyTargetAccess
- verifySiteAccess runs before the target create/update handler

Test security scenarios for both WireGuard and newt site types.

Signed-off-by: Marc Schäfer <git@marcschaeferger.de>
2026-05-29 22:57:39 +00:00
Marc Schäfer 51629247a5 fix(middleware): prevent cross-org site binding in target create/update
Extend verifySiteAccess to check that when req.userOrgId is already set
by a prior middleware (e.g. verifyResourceAccess/verifyTargetAccess), the
site from req.body.siteId belongs to the same organization. This prevents
the cross-organization tunnel boundary bypass where an attacker with
resource access in one org binds that resource's target to a site in
another org.

Add verifySiteAccess to both target route stacks:
- PUT /resource/:resourceId/target (after verifyResourceAccess)
- POST /target/:targetId (after verifyTargetAccess)

The org-match check runs before req.userOrg is overwritten, so the
resource's organization context is preserved for comparison.

Signed-off-by: Marc Schäfer <git@marcschaeferger.de>
2026-05-29 22:44:16 +00:00
353 changed files with 16392 additions and 16344 deletions
+5
View File
@@ -0,0 +1,5 @@
---
alwaysApply: true
---
When adding submit buttons, don't change the text of the button during the loading state. Text should stay static and you should use the loading prop on the button.
+5
View File
@@ -0,0 +1,5 @@
---
alwaysApply: true
---
When creating UI for popup dialogs or modals, use the Credenza componennt. This component is mobile responsive and works on desktop and wraps the dialog component and sheet into one.
+7
View File
@@ -0,0 +1,7 @@
---
alwaysApply: true
---
When writing TypeScript:
Prefer to use types instead of interfaces.
@@ -0,0 +1,5 @@
---
alwaysApply: true
---
When creating forms, use React form for validation and use Zod schemas.
+2 -1
View File
@@ -34,4 +34,5 @@ build.ts
tsconfig.json tsconfig.json
Dockerfile* Dockerfile*
drizzle.config.ts drizzle.config.ts
allowedDevOrigins.json allowedDevOrigins.json
scratch/
+41 -7
View File
@@ -4,19 +4,26 @@ import { eq } from "drizzle-orm";
type SetServerAdminArgs = { type SetServerAdminArgs = {
email: string; email: string;
remove: boolean;
}; };
export const setServerAdmin: CommandModule<{}, SetServerAdminArgs> = { export const setServerAdmin: CommandModule<{}, SetServerAdminArgs> = {
command: "set-server-admin", command: "set-server-admin",
describe: "Mark any user as a server admin by email address", describe: "Add or remove server admin by email address",
builder: (yargs) => { builder: (yargs) => {
return yargs.option("email", { return yargs
type: "string", .option("email", {
demandOption: true, type: "string",
describe: "User email address" demandOption: true,
}); describe: "User email address"
})
.option("remove", {
type: "boolean",
default: false,
describe: "Remove server admin status from the user"
});
}, },
handler: async (argv: { email: string }) => { handler: async (argv: SetServerAdminArgs) => {
try { try {
const email = argv.email.trim().toLowerCase(); const email = argv.email.trim().toLowerCase();
@@ -31,6 +38,33 @@ export const setServerAdmin: CommandModule<{}, SetServerAdminArgs> = {
process.exit(1); process.exit(1);
} }
if (argv.remove) {
if (!user.serverAdmin) {
console.log(`User '${email}' is not a server admin`);
process.exit(0);
}
const serverAdmins = await db
.select()
.from(users)
.where(eq(users.serverAdmin, true));
if (serverAdmins.length <= 1) {
console.error(
"Cannot remove server admin: at least one server admin must exist"
);
process.exit(1);
}
await db
.update(users)
.set({ serverAdmin: false })
.where(eq(users.userId, user.userId));
console.log(`Server admin status removed from user '${email}'`);
process.exit(0);
}
if (user.serverAdmin) { if (user.serverAdmin) {
console.log(`User '${email}' is already a server admin`); console.log(`User '${email}' is already a server admin`);
process.exit(0); process.exit(0);
+2 -4
View File
@@ -38,7 +38,5 @@ flags:
disable_user_create_org: false disable_user_create_org: false
allow_raw_resources: true allow_raw_resources: true
{{if .IsPostgreSQL}} {{if .IsPostgreSQL}}postgres:
postgres: connection_string: postgresql://pangolin:{{.IsPostgreSQLPass}}@postgres:5432/pangolin{{end}}
connection_string: postgresql://pangolin:{{.IsPostgreSQLPass}}@postgres:5432/pangolin
{{end}}
+20 -33
View File
@@ -7,23 +7,17 @@ services:
deploy: deploy:
resources: resources:
limits: limits:
memory: 1g memory: 2g
reservations: reservations:
memory: 256m memory: 512m
{{if or .IsPostgreSQL .IsRedis}} {{if or .IsPostgreSQL .IsRedis}}depends_on:
depends_on: {{if .IsPostgreSQL}}postgres:
{{if .IsPostgreSQL}} condition: service_healthy{{end}}
postgres: {{if .IsRedis}}redis:
condition: service_healthy condition: service_healthy{{end}}
{{end}}
{{if .IsRedis}}
redis:
condition: service_healthy
{{end}}
networks: networks:
- default - default
- backend - backend{{end}}
{{end}}
volumes: volumes:
- ./config:/app/config - ./config:/app/config
healthcheck: healthcheck:
@@ -31,8 +25,8 @@ services:
interval: "10s" interval: "10s"
timeout: "10s" timeout: "10s"
retries: 15 retries: 15
{{if .InstallGerbil}}
gerbil: {{if .InstallGerbil}}gerbil:
image: docker.io/fosrl/gerbil:{{.GerbilVersion}} image: docker.io/fosrl/gerbil:{{.GerbilVersion}}
container_name: gerbil container_name: gerbil
restart: unless-stopped restart: unless-stopped
@@ -53,17 +47,16 @@ services:
- 21820:21820/udp - 21820:21820/udp
- 443:443 - 443:443
- 443:443/udp # For http3 QUIC if desired - 443:443/udp # For http3 QUIC if desired
- 80:80 - 80:80{{end}}
{{end}}
traefik: traefik:
image: docker.io/traefik:v3.6 image: docker.io/traefik:v3.6
container_name: traefik container_name: traefik
restart: unless-stopped restart: unless-stopped
{{if .InstallGerbil}} network_mode: service:gerbil # Ports appear on the gerbil service{{end}}{{if not .InstallGerbil}} {{if .InstallGerbil}}network_mode: service:gerbil # Ports appear on the gerbil service{{end}}{{if not .InstallGerbil}}
ports: ports:
- 443:443 - 443:443
- 80:80 - 80:80{{end}}
{{end}}
depends_on: depends_on:
pangolin: pangolin:
condition: service_healthy condition: service_healthy
@@ -74,8 +67,7 @@ services:
- ./config/letsencrypt:/letsencrypt # Volume to store the Let's Encrypt certificates - ./config/letsencrypt:/letsencrypt # Volume to store the Let's Encrypt certificates
- ./config/traefik/logs:/var/log/traefik # Volume to store Traefik logs - ./config/traefik/logs:/var/log/traefik # Volume to store Traefik logs
{{if .IsPostgreSQL}} {{if .IsPostgreSQL}}postgres:
postgres:
image: postgres:18 image: postgres:18
container_name: postgres container_name: postgres
restart: unless-stopped restart: unless-stopped
@@ -91,11 +83,9 @@ services:
timeout: 5s timeout: 5s
retries: 5 retries: 5
networks: networks:
- backend - backend{{end}}
{{end}}
{{if .IsRedis}} {{if .IsRedis}}redis:
redis:
image: redis:8-trixie image: redis:8-trixie
container_name: redis container_name: redis
restart: unless-stopped restart: unless-stopped
@@ -113,17 +103,14 @@ services:
retries: 3 retries: 3
start_period: 10s start_period: 10s
networks: networks:
- backend - backend{{end}}
{{end}}
networks: networks:
default: default:
driver: bridge driver: bridge
name: pangolin_frontend name: pangolin_frontend
{{if .EnableIPv6}} enable_ipv6: true{{end}} {{if .EnableIPv6}} enable_ipv6: true{{end}}
{{if or .IsPostgreSQL .IsRedis}} {{if or .IsPostgreSQL .IsRedis}} backend:
backend:
driver: bridge driver: bridge
name: pangolin_backend name: pangolin_backend
internal: true internal: true{{end}}
{{end}}
+2 -4
View File
@@ -1,6 +1,4 @@
{{if .IsRedis}} {{if .IsRedis}}redis:
redis:
host: "redis" host: "redis"
port: 6379 port: 6379
password: "{{.IsRedisPass}}" password: "{{.IsRedisPass}}"{{end}}
{{end}}
+7 -4
View File
@@ -71,9 +71,12 @@ const (
Undefined SupportedContainer = "undefined" Undefined SupportedContainer = "undefined"
) )
var redisFlag *bool
func main() { func main() {
crowdsecFlag := flag.Bool("crowdsec", false, "Enable the CrowdSec installation prompt") crowdsecFlag := flag.Bool("crowdsec", false, "Enable the CrowdSec installation prompt")
redisFlag = flag.Bool("redis", false, "Install Redis as cacheing solution. Required for HA. Not required for the Enterprise version.")
flag.Parse() flag.Parse()
// print a banner about prerequisites - opening port 80, 443, 51820, and 21820 on the VPS and firewall and pointing your domain to the VPS IP with a records. Docs are at http://localhost:3000/Getting%20Started/dns-networking // print a banner about prerequisites - opening port 80, 443, 51820, and 21820 on the VPS and firewall and pointing your domain to the VPS IP with a records. Docs are at http://localhost:3000/Getting%20Started/dns-networking
@@ -491,13 +494,13 @@ func collectUserInput() Config {
config.IsEnterprise = readBoolNoDefault("Do you want to install the Enterprise version of Pangolin? The EE is free for personal use or for businesses making less than 100k USD annually.") config.IsEnterprise = readBoolNoDefault("Do you want to install the Enterprise version of Pangolin? The EE is free for personal use or for businesses making less than 100k USD annually.")
if config.IsEnterprise { if config.IsEnterprise {
config.IsRedis = readBool("Do you want to run the Redis containers locally? Required for HA.") if *redisFlag {
if config.IsRedis { config.IsRedis = true
config.IsRedisPass = readPassword("Enter a unique password for the Redis service.") config.IsRedisPass = readPassword("Enter a unique password for the Redis service.")
} }
} }
config.IsPostgreSQL = readBool("Do you want to run the PostgreSQL containers locally? Otherwise, default to the local SQLite database only.", false) config.IsPostgreSQL = readBool("Do you want to use PostgreSQL (not recommended for most users)?", false)
if config.IsPostgreSQL { if config.IsPostgreSQL {
config.IsPostgreSQLPass = readPassword("Enter a unique password for the PostgreSQL pangolin user.") config.IsPostgreSQLPass = readPassword("Enter a unique password for the PostgreSQL pangolin user.")
} }
@@ -544,7 +547,7 @@ func collectUserInput() Config {
fmt.Println("\n=== Advanced Configuration ===") fmt.Println("\n=== Advanced Configuration ===")
config.EnableIPv6 = readBool("Is your server IPv6 capable?", true) config.EnableIPv6 = readBool("Is your server IPv6 capable?", true)
config.EnableMaxMind = readBool("Do you want to download the MaxMind GeoLite2 Country and ADN databases for blocking functionality?", true) config.EnableMaxMind = readBool("Do you want to download the MaxMind GeoLite2 Country and ASN databases for blocking functionality?", true)
if config.DashboardDomain == "" { if config.DashboardDomain == "" {
fmt.Println("Error: Dashboard Domain name is required") fmt.Println("Error: Dashboard Domain name is required")
+27 -123
View File
@@ -101,8 +101,6 @@
"sitesTableViewPrivateResources": "Вижте частни ресурси", "sitesTableViewPrivateResources": "Вижте частни ресурси",
"siteInstallNewt": "Инсталирайте Newt", "siteInstallNewt": "Инсталирайте Newt",
"siteInstallNewtDescription": "Пуснете Newt на вашата система", "siteInstallNewtDescription": "Пуснете Newt на вашата система",
"siteInstallKubernetesDocsDescription": "For more and up to date Kubernetes installation information, see <docsLink>docs.pangolin.net/manage/sites/install-kubernetes</docsLink>.",
"siteInstallAdvantechDocsDescription": "For Advantech modem installation instructions, see <docsLink>docs.pangolin.net/manage/sites/install-advantech</docsLink>.",
"WgConfiguration": "WireGuard конфигурация", "WgConfiguration": "WireGuard конфигурация",
"WgConfigurationDescription": "Използвайте следната конфигурация, за да се свържете с мрежата", "WgConfigurationDescription": "Използвайте следната конфигурация, за да се свържете с мрежата",
"operatingSystem": "Операционна система", "operatingSystem": "Операционна система",
@@ -202,8 +200,8 @@
"shareErrorSelectResource": "Моля, изберете ресурс", "shareErrorSelectResource": "Моля, изберете ресурс",
"proxyResourceTitle": "Управление на обществени ресурси", "proxyResourceTitle": "Управление на обществени ресурси",
"proxyResourceDescription": "Създайте и управлявайте ресурси, които са общодостъпни чрез уеб браузър.", "proxyResourceDescription": "Създайте и управлявайте ресурси, които са общодостъпни чрез уеб браузър.",
"publicResourcesBannerTitle": "Web-based Public Access", "publicResourcesBannerTitle": "Публичен достъп чрез уеб.",
"publicResourcesBannerDescription": "Public resources are HTTPS proxies accessible to anyone on the internet through a web browser. Unlike private resources, they do not require client-side software and can include identity and context-aware access policies.", "publicResourcesBannerDescription": "Публичните ресурси са HTTPS или TCP/UDP проксита, достъпни за всеки в интернет чрез уеб браузър. За разлика от частните ресурси, те не изискват софтуер от страна на клиента и могат да включват издентити и контексто-осъзнати политики за достъп.",
"clientResourceTitle": "Управление на частни ресурси", "clientResourceTitle": "Управление на частни ресурси",
"clientResourceDescription": "Създайте и управлявайте ресурси, които са достъпни само чрез свързан клиент.", "clientResourceDescription": "Създайте и управлявайте ресурси, които са достъпни само чрез свързан клиент.",
"privateResourcesBannerTitle": "Достъп до частни ресурси с нулево доверие.", "privateResourcesBannerTitle": "Достъп до частни ресурси с нулево доверие.",
@@ -211,16 +209,15 @@
"resourcesSearch": "Търсене на ресурси...", "resourcesSearch": "Търсене на ресурси...",
"resourceAdd": "Добавете ресурс", "resourceAdd": "Добавете ресурс",
"resourceErrorDelte": "Грешка при изтриване на ресурс", "resourceErrorDelte": "Грешка при изтриване на ресурс",
"resourcePoliciesTitle": "Manage Public Resource Policies", "resourcePoliciesTitle": "Управление на политики за ресурси",
"resourcePoliciesAttachedResourcesColumnTitle": "Resources", "resourcePoliciesAttachedResourcesColumnTitle": "Свързани ресурси",
"resourcePoliciesAttachedResources": "{count} ресурс(а)", "resourcePoliciesAttachedResources": "{count} ресурс(а)",
"resourcePoliciesAttachedResourcesCount": "{count, plural, one {# resource} other {# resources}}",
"resourcePoliciesAttachedResourcesEmpty": "няма ресурси", "resourcePoliciesAttachedResourcesEmpty": "няма ресурси",
"resourcePoliciesDescription": "Create and manage authentication policies to control access to your public resources", "resourcePoliciesDescription": "Създавай и управлявай политики за автентикация, за да контролирате достъпа до вашите ресурси",
"resourcePoliciesSearch": "Търсене на политики...", "resourcePoliciesSearch": "Търсене на политики...",
"resourcePoliciesAdd": "Добавяне на политика", "resourcePoliciesAdd": "Добавяне на политика",
"resourcePoliciesDefaultBadgeText": "Стандартна политика", "resourcePoliciesDefaultBadgeText": "Стандартна политика",
"resourcePoliciesCreate": "Create Public Resource Policy", "resourcePoliciesCreate": "Създаване на политика за ресурс",
"resourcePoliciesCreateDescription": "Следвайте стъпките по-долу, за да създадете нова политика", "resourcePoliciesCreateDescription": "Следвайте стъпките по-долу, за да създадете нова политика",
"resourcePolicyName": "Име на политика", "resourcePolicyName": "Име на политика",
"resourcePolicyNameDescription": "Дайте на тази политика име, за да я идентифицирате в цялото ви ресурси", "resourcePolicyNameDescription": "Дайте на тази политика име, за да я идентифицирате в цялото ви ресурси",
@@ -314,7 +311,7 @@
"rules": "Правила", "rules": "Правила",
"resourceSettingDescription": "Конфигурирайте настройките на ресурса", "resourceSettingDescription": "Конфигурирайте настройките на ресурса",
"resourceSetting": "Настройки на {resourceName}", "resourceSetting": "Настройки на {resourceName}",
"resourcePolicySettingDescription": "Configure the settings on this public resource policy", "resourcePolicySettingDescription": "Конфигурирайте настройките на политиката за ресурс",
"resourcePolicySetting": "Настройки за {policyName}", "resourcePolicySetting": "Настройки за {policyName}",
"alwaysAllow": "Заобикаляне на Ауторизацията", "alwaysAllow": "Заобикаляне на Ауторизацията",
"alwaysDeny": "Блокиране на Достъпа", "alwaysDeny": "Блокиране на Достъпа",
@@ -756,11 +753,11 @@
"rulesErrorDuplicate": "Дубликат на правило", "rulesErrorDuplicate": "Дубликат на правило",
"rulesErrorDuplicateDescription": "Правило с тези настройки вече съществува", "rulesErrorDuplicateDescription": "Правило с тези настройки вече съществува",
"rulesErrorInvalidIpAddressRange": "Невалиден CIDR", "rulesErrorInvalidIpAddressRange": "Невалиден CIDR",
"rulesErrorInvalidIpAddressRangeDescription": "Enter a valid CIDR range (e.g., 10.0.0.0/8).", "rulesErrorInvalidIpAddressRangeDescription": "Моля, въведете валидна стойност на CIDR",
"rulesErrorInvalidUrl": "Invalid path", "rulesErrorInvalidUrl": "Невалиден URL път",
"rulesErrorInvalidUrlDescription": "Enter a valid URL path or pattern (e.g., /api/*).", "rulesErrorInvalidUrlDescription": "Моля, въведете валидна стойност за URL път",
"rulesErrorInvalidIpAddress": "Invalid IP address", "rulesErrorInvalidIpAddress": "Невалиден IP",
"rulesErrorInvalidIpAddressDescription": "Enter a valid IPv4 or IPv6 address.", "rulesErrorInvalidIpAddressDescription": "Моля, въведете валиден IP адрес",
"rulesErrorUpdate": "Неуспешно актуализиране на правилата", "rulesErrorUpdate": "Неуспешно актуализиране на правилата",
"rulesErrorUpdateDescription": "Възникна грешка при актуализиране на правилата", "rulesErrorUpdateDescription": "Възникна грешка при актуализиране на правилата",
"rulesUpdated": "Активиране на правилата", "rulesUpdated": "Активиране на правилата",
@@ -768,23 +765,15 @@
"rulesMatchIpAddressRangeDescription": "Въведете адрес във формат CIDR (напр. 103.21.244.0/22)", "rulesMatchIpAddressRangeDescription": "Въведете адрес във формат CIDR (напр. 103.21.244.0/22)",
"rulesMatchIpAddress": "Въведете IP адрес (напр. 103.21.244.12)", "rulesMatchIpAddress": "Въведете IP адрес (напр. 103.21.244.12)",
"rulesMatchUrl": "Въведете URL път или модел (напр. /api/v1/todos или /api/v1/*)", "rulesMatchUrl": "Въведете URL път или модел (напр. /api/v1/todos или /api/v1/*)",
"rulesErrorInvalidPriority": "Invalid priority", "rulesErrorInvalidPriority": "Невалиден приоритет",
"rulesErrorInvalidPriorityDescription": "Enter a whole number of 1 or higher.", "rulesErrorInvalidPriorityDescription": "Моля, въведете валиден приоритет",
"rulesErrorDuplicatePriority": "Duplicate priorities", "rulesErrorDuplicatePriority": "Дублирани приоритети",
"rulesErrorDuplicatePriorityDescription": "Each rule must have a unique priority number.", "rulesErrorDuplicatePriorityDescription": "Моля, въведете уникални приоритети",
"rulesErrorValidation": "Invalid rules",
"rulesErrorValidationRuleDescription": "Rule {ruleNumber}: {message}",
"rulesErrorValueRequired": "Enter a value for this rule.",
"rulesErrorInvalidCountry": "Invalid country",
"rulesErrorInvalidCountryDescription": "Select a valid country.",
"rulesErrorInvalidAsn": "Invalid ASN",
"rulesErrorInvalidAsnDescription": "Enter a valid ASN (e.g., AS15169).",
"ruleUpdated": "Правилата са актуализирани", "ruleUpdated": "Правилата са актуализирани",
"ruleUpdatedDescription": "Правилата бяха успешно актуализирани", "ruleUpdatedDescription": "Правилата бяха успешно актуализирани",
"ruleErrorUpdate": "Операцията не бе успешна", "ruleErrorUpdate": "Операцията не бе успешна",
"ruleErrorUpdateDescription": "Възникна грешка по време на операцията за запис", "ruleErrorUpdateDescription": "Възникна грешка по време на операцията за запис",
"rulesPriority": "Приоритет", "rulesPriority": "Приоритет",
"rulesReorderDragHandle": "Drag to reorder rule priority",
"rulesAction": "Действие", "rulesAction": "Действие",
"rulesMatchType": "Тип на съвпадение", "rulesMatchType": "Тип на съвпадение",
"value": "Стойност", "value": "Стойност",
@@ -803,7 +792,7 @@
"rulesResource": "Конфигурация на правилата за ресурси", "rulesResource": "Конфигурация на правилата за ресурси",
"rulesResourceDescription": "Конфигурирайте правила за контролиране на достъпа до ресурса", "rulesResourceDescription": "Конфигурирайте правила за контролиране на достъпа до ресурса",
"ruleSubmit": "Добави правило", "ruleSubmit": "Добави правило",
"rulesNoOne": "No rules yet.", "rulesNoOne": "Няма правила. Добавете правило чрез формуляра.",
"rulesOrder": "Правилата се оценяват по приоритет в нарастващ ред.", "rulesOrder": "Правилата се оценяват по приоритет в нарастващ ред.",
"rulesSubmit": "Запазване на правилата", "rulesSubmit": "Запазване на правилата",
"policyErrorCreate": "Грешка при създаване на политика", "policyErrorCreate": "Грешка при създаване на политика",
@@ -814,44 +803,7 @@
"policyErrorUpdateMessageDescription": "Възникна неочаквана грешка", "policyErrorUpdateMessageDescription": "Възникна неочаквана грешка",
"policyCreatedSuccess": "Политиката за ресурс е създадена успешно", "policyCreatedSuccess": "Политиката за ресурс е създадена успешно",
"policyUpdatedSuccess": "Политиката за ресурс е актуализирана успешно", "policyUpdatedSuccess": "Политиката за ресурс е актуализирана успешно",
"authMethodsSave": "Save Settings", "authMethodsSave": "Запазете методите за идентификация",
"policyAuthStackTitle": "Authentication",
"policyAuthStackDescription": "Control which authentication methods are required to access this resource",
"policyAuthOrLogicTitle": "Multiple authentication methods active",
"policyAuthOrLogicBanner": "Visitors may authenticate using any one of the active methods below. They do not need to complete all of them.",
"policyAuthMethodActive": "Active",
"policyAuthMethodOff": "Off",
"policyAuthSsoTitle": "Platform SSO",
"policyAuthSsoDescription": "Require sign-in through your organization's identity provider",
"policyAuthSsoSummary": "{idp} · {users} users, {roles} roles",
"policyAuthSsoDefaultIdp": "Default provider",
"policyAuthAddDefaultIdentityProvider": "Add Default Identity Provider",
"policyAuthOtherMethodsTitle": "Other Methods",
"policyAuthOtherMethodsDescription": "Optional methods visitors can use instead of or alongside platform SSO",
"policyAuthPasscodeTitle": "Passcode",
"policyAuthPasscodeDescription": "Require a shared alphanumeric passcode to access the resource",
"policyAuthPasscodeSummary": "Passcode set",
"policyAuthPincodeTitle": "PIN Code",
"policyAuthPincodeDescription": "A short numeric code required to access the resource",
"policyAuthPincodeSummary": "6-digit PIN set",
"policyAuthEmailTitle": "Email Whitelist",
"policyAuthEmailDescription": "Allow listed email addresses with one-time passwords",
"policyAuthEmailSummary": "{count} addresses allowed",
"policyAuthEmailOtpCallout": "Enabling email whitelist sends a one-time password to the visitor's email on login.",
"policyAuthHeaderAuthTitle": "Basic Header Auth",
"policyAuthHeaderAuthDescription": "Validate a custom HTTP header name and value on each request",
"policyAuthHeaderAuthSummary": "Header configured",
"policyAuthHeaderName": "Header name",
"policyAuthHeaderValue": "Expected value",
"policyAccessRulesTitle": "Access Rules",
"policyAccessRulesEnableDescription": "When enabled, rules are evaluated in descending order until one evaluates as true.",
"policyAccessRulesFirstMatch": "Rules are evaluated top to bottom. The first matching rule decides the outcome.",
"policyAccessRulesHowItWorks": "Rules match requests by path, IP address, location, or other criteria. Each rule applies an action: bypass authentication, block access, or pass to authentication. If no rule matches, traffic continues to authentication.",
"policyAccessRulesFallthroughOff": "When rules are disabled, all traffic passes through to authentication.",
"policyAccessRulesFallthroughOn": "When no rule matches, traffic passes through to authentication.",
"rulesPlaceholderCidr": "10.0.0.0/8",
"rulesPlaceholderPath": "/admin/*",
"rulesPlaceholderGeo": "RU, KP",
"rulesSave": "Запазете правилата", "rulesSave": "Запазете правилата",
"resourceErrorCreate": "Грешка при създаване на ресурс", "resourceErrorCreate": "Грешка при създаване на ресурс",
"resourceErrorCreateDescription": "Възникна грешка при създаването на ресурса", "resourceErrorCreateDescription": "Възникна грешка при създаването на ресурса",
@@ -1268,10 +1220,8 @@
"addLabels": "Добавяне на етикети", "addLabels": "Добавяне на етикети",
"siteLabelsTab": "Етикети", "siteLabelsTab": "Етикети",
"siteLabelsDescription": "Управление на етикети, свързани с този сайт.", "siteLabelsDescription": "Управление на етикети, свързани с този сайт.",
"labelsNotFound": "No labels found.", "labelsNotFound": "Етикети не са намерени",
"labelsEmptyCreateHint": "Start typing above to create a label.",
"labelSearch": "Търсене на етикети", "labelSearch": "Търсене на етикети",
"labelSearchOrCreate": "Search or create a label",
"accessLabelFilterCount": "{count, plural, one {# етикет} other {# етикети}}", "accessLabelFilterCount": "{count, plural, one {# етикет} other {# етикети}}",
"labelOverflowCount": "+{count, plural, one {# етикет} other {# етикети}}", "labelOverflowCount": "+{count, plural, one {# етикет} other {# етикети}}",
"accessLabelFilterClear": "Изчисти филтрите за етикети", "accessLabelFilterClear": "Изчисти филтрите за етикети",
@@ -1512,7 +1462,7 @@
"sidebarProxyResources": "Публично", "sidebarProxyResources": "Публично",
"sidebarClientResources": "Частно", "sidebarClientResources": "Частно",
"sidebarPolicies": "Политики", "sidebarPolicies": "Политики",
"sidebarResourcePolicies": "Public Resources", "sidebarResourcePolicies": "Ресурси",
"sidebarAccessControl": "Контрол на достъпа", "sidebarAccessControl": "Контрол на достъпа",
"sidebarLogsAndAnalytics": "Дневници и анализи", "sidebarLogsAndAnalytics": "Дневници и анализи",
"sidebarTeam": "Екип", "sidebarTeam": "Екип",
@@ -1520,7 +1470,7 @@
"sidebarAdmin": "Администратор", "sidebarAdmin": "Администратор",
"sidebarInvitations": "Покани", "sidebarInvitations": "Покани",
"sidebarRoles": "Роли", "sidebarRoles": "Роли",
"sidebarShareableLinks": "Share Links", "sidebarShareableLinks": "Връзки",
"sidebarApiKeys": "API ключове", "sidebarApiKeys": "API ключове",
"sidebarProvisioning": "Осигуряване", "sidebarProvisioning": "Осигуряване",
"sidebarSettings": "Настройки", "sidebarSettings": "Настройки",
@@ -1697,7 +1647,7 @@
"standaloneHcFilterResourceIdFallback": "Ресурс {id}", "standaloneHcFilterResourceIdFallback": "Ресурс {id}",
"blueprints": "Чертежи", "blueprints": "Чертежи",
"blueprintsLog": "Регистър на скицописи", "blueprintsLog": "Регистър на скицописи",
"blueprintsDescription": "View past blueprint applications and their results or apply a new blueprint", "blueprintsDescription": "Вижте предишни приложения и техните резултати",
"blueprintAdd": "Добави Чертеж", "blueprintAdd": "Добави Чертеж",
"blueprintGoBack": "Виж всички Чертежи", "blueprintGoBack": "Виж всички Чертежи",
"blueprintCreate": "Създай Чертеж", "blueprintCreate": "Създай Чертеж",
@@ -2077,13 +2027,13 @@
"healthCheckUnknown": "Неизвестен", "healthCheckUnknown": "Неизвестен",
"healthCheck": "Проверка на здравето", "healthCheck": "Проверка на здравето",
"configureHealthCheck": "Конфигуриране на проверка на здравето", "configureHealthCheck": "Конфигуриране на проверка на здравето",
"configureHealthCheckDescription": "Set up monitoring for your resource to ensure it is always available", "configureHealthCheckDescription": "Настройте мониторинг на здравето за {target}",
"enableHealthChecks": "Разрешаване на проверки на здравето", "enableHealthChecks": "Разрешаване на проверки на здравето",
"healthCheckDisabledStateDescription": "Когато е деактивиран, сайтът не изпълнява проверки и състоянието се счита за неизвестно.", "healthCheckDisabledStateDescription": "Когато е деактивиран, сайтът не изпълнява проверки и състоянието се счита за неизвестно.",
"enableHealthChecksDescription": "Мониторинг на здравето на тази цел. Можете да наблюдавате различен краен пункт от целта, ако е необходимо.", "enableHealthChecksDescription": "Мониторинг на здравето на тази цел. Можете да наблюдавате различен краен пункт от целта, ако е необходимо.",
"healthScheme": "Метод", "healthScheme": "Метод",
"healthSelectScheme": "Избор на метод", "healthSelectScheme": "Избор на метод",
"healthCheckPortInvalid": "Port must be between 1 and 65535", "healthCheckPortInvalid": "Портът за проверка на състоянието трябва да е между 1 и 65535",
"healthCheckPath": "Път", "healthCheckPath": "Път",
"healthHostname": "IP / Хост", "healthHostname": "IP / Хост",
"healthPort": "Порт", "healthPort": "Порт",
@@ -2096,7 +2046,6 @@
"requireDeviceApproval": "Изискват одобрение на устройства", "requireDeviceApproval": "Изискват одобрение на устройства",
"requireDeviceApprovalDescription": "Потребители с тази роля трябва да имат нови устройства одобрени от администратор преди да могат да се свържат и да имат достъп до ресурси.", "requireDeviceApprovalDescription": "Потребители с тази роля трябва да имат нови устройства одобрени от администратор преди да могат да се свържат и да имат достъп до ресурси.",
"sshSettings": "Настройки за SSH", "sshSettings": "Настройки за SSH",
"sshAccess": "SSH Access",
"rdpSettings": "Настройки за RDP", "rdpSettings": "Настройки за RDP",
"vncSettings": "Настройки за VNC", "vncSettings": "Настройки за VNC",
"sshServer": "SSH сървър", "sshServer": "SSH сървър",
@@ -2123,13 +2072,8 @@
"sshDaemonDisclaimer": "Уверете се, че вашата целева хост машина е правилно конфигурирана за изпълнение на демона за идентификация преди завършване на тази настройка, в противен случай осигуряването ще се провали.", "sshDaemonDisclaimer": "Уверете се, че вашата целева хост машина е правилно конфигурирана за изпълнение на демона за идентификация преди завършване на тази настройка, в противен случай осигуряването ще се провали.",
"sshDaemonPort": "Порт на демона", "sshDaemonPort": "Порт на демона",
"sshServerDestination": "Дестинация на сървъра", "sshServerDestination": "Дестинация на сървъра",
"sshServerDestinationDescription": "Configure the destination of the SSH server", "sshServerDestinationDescription": "Конфигуриране на дестинацията и порта на SSH сървъра",
"destination": "Дестинация", "destination": "Дестинация",
"destinationRequired": "Destination is required.",
"domainRequired": "Domain is required.",
"proxyPortRequired": "Port is required.",
"invalidPathConfiguration": "Invalid path configuration.",
"invalidRewritePathConfiguration": "Invalid rewrite path configuration.",
"bgTargetMultiSiteDisclaimer": "Избиране на множество сайтове позволява устойчиво маршрутизиране и сокетно превключване за висока наличност.", "bgTargetMultiSiteDisclaimer": "Избиране на множество сайтове позволява устойчиво маршрутизиране и сокетно превключване за висока наличност.",
"roleAllowSsh": "Разреши SSH", "roleAllowSsh": "Разреши SSH",
"roleAllowSshAllow": "Разреши", "roleAllowSshAllow": "Разреши",
@@ -3090,7 +3034,7 @@
"enterConfirmation": "Въведете потвърждение.", "enterConfirmation": "Въведете потвърждение.",
"blueprintViewDetails": "Подробности.", "blueprintViewDetails": "Подробности.",
"defaultIdentityProvider": "По подразбиране доставчик на идентичност.", "defaultIdentityProvider": "По подразбиране доставчик на идентичност.",
"defaultIdentityProviderDescription": "The user will be automatically redirected to this identity provider for authentication.", "defaultIdentityProviderDescription": "Когато е избран основен доставчик на идентичност, потребителят ще бъде автоматично пренасочен към доставчика за удостоверяване.",
"editInternalResourceDialogNetworkSettings": "Мрежови настройки.", "editInternalResourceDialogNetworkSettings": "Мрежови настройки.",
"editInternalResourceDialogAccessPolicy": "Политика за достъп.", "editInternalResourceDialogAccessPolicy": "Политика за достъп.",
"editInternalResourceDialogAddRoles": "Добавяне на роли.", "editInternalResourceDialogAddRoles": "Добавяне на роли.",
@@ -3402,8 +3346,6 @@
"idpUnassociateQuestion": "Сигурни ли сте, че искате да отвържете този доставчик на самоличност от тази организация?", "idpUnassociateQuestion": "Сигурни ли сте, че искате да отвържете този доставчик на самоличност от тази организация?",
"idpUnassociateDescription": "Всички потребители, свързани с този доставчик на самоличност, ще бъдат премахнати от тази организация, но доставчика на самоличност ще продължи да съществува за други свързани организации.", "idpUnassociateDescription": "Всички потребители, свързани с този доставчик на самоличност, ще бъдат премахнати от тази организация, но доставчика на самоличност ще продължи да съществува за други свързани организации.",
"idpUnassociateConfirm": "Потвърдете отвързване на доставчика на самоличност", "idpUnassociateConfirm": "Потвърдете отвързване на доставчика на самоличност",
"idpConfirmDeleteAndRemoveMeFromOrg": "DELETE AND REMOVE ME FROM ORG",
"idpUnassociateAndRemoveMeFromOrg": "UNASSOCIATE AND REMOVE ME FROM ORG",
"idpUnassociateWarning": "Това не може да бъде отменено за тази организация.", "idpUnassociateWarning": "Това не може да бъде отменено за тази организация.",
"idpUnassociatedDescription": "Доставчика на самоличност е успешно отвързан от тази организация", "idpUnassociatedDescription": "Доставчика на самоличност е успешно отвързан от тази организация",
"idpUnassociateMenu": "Отвързване", "idpUnassociateMenu": "Отвързване",
@@ -3510,43 +3452,5 @@
"sshErrorNoTarget": "Няма посочена цел", "sshErrorNoTarget": "Няма посочена цел",
"sshErrorWebSocket": "Неуспешно създаване на WebSocket връзка", "sshErrorWebSocket": "Неуспешно създаване на WebSocket връзка",
"sshErrorAuthFailed": "Неуспешна идентификация", "sshErrorAuthFailed": "Неуспешна идентификация",
"sshErrorConnectionClosed": "Връзката е затворена преди завършване на идентификацията", "sshErrorConnectionClosed": "Връзката е затворена преди завършване на идентификацията"
"sitePangolinSshDescription": "Allow SSH access to resources on this site. This can be changed later.",
"browserGatewayNoResourceForDomain": "No resource found for this domain",
"browserGatewayNoTarget": "No target",
"browserGatewayConnect": "Connect",
"browserGatewayCtrlAltDel": "Ctrl+Alt+Del",
"sshErrorSignKeyFailed": "Failed to sign SSH key for PAM push authentication. Did you sign in as a user?",
"sshTerminalError": "Error: {error}",
"sshConnectionClosedCode": "Connection closed (code {code})",
"sshPrivateKeyPlaceholder": "-----BEGIN OPENSSH PRIVATE KEY-----",
"sshPrivateKeyRequired": "Private key is required",
"vncTitle": "VNC",
"vncSignInDescription": "Enter your VNC password to connect",
"vncPasswordOptional": "Password (optional)",
"vncNoResourceTarget": "No resource target is available",
"vncFailedToLoadNovnc": "Failed to load noVNC",
"vncAuthFailedStatus": "Status {status}",
"vncPasteClipboard": "Paste clipboard",
"rdpTitle": "RDP",
"rdpSignInTitle": "Sign in to Remote Desktop",
"rdpSignInDescription": "Enter Windows credentials to connect",
"rdpLoadingModule": "Loading module...",
"rdpFailedToLoadModule": "Failed to load RDP module",
"rdpNotReady": "Not ready",
"rdpModuleInitializing": "RDP module is still initializing",
"rdpDownloadingFiles": "Downloading {count} file(s) from remote…",
"rdpDownloadFailed": "Download failed: {fileName}",
"rdpUploaded": "Uploaded: {fileName}",
"rdpNoConnectionTarget": "No connection target available",
"rdpConnectionFailed": "Connection failed",
"rdpFit": "Fit",
"rdpFull": "Full",
"rdpReal": "Real",
"rdpMeta": "Meta",
"rdpUploadFiles": "Upload files",
"rdpFilesReadyToPaste": "Files ready to paste",
"rdpFilesReadyToPasteDescription": "{count} file(s) copied to remote clipboard — press Ctrl+V on the remote desktop to paste.",
"rdpUploadFailed": "Upload failed",
"rdpUnicodeKeyboardMode": "Unicode keyboard mode"
} }
+27 -123
View File
@@ -101,8 +101,6 @@
"sitesTableViewPrivateResources": "Zobrazit soukromé zdroje", "sitesTableViewPrivateResources": "Zobrazit soukromé zdroje",
"siteInstallNewt": "Nainstalovat Newt", "siteInstallNewt": "Nainstalovat Newt",
"siteInstallNewtDescription": "Spustit Newt na vašem systému", "siteInstallNewtDescription": "Spustit Newt na vašem systému",
"siteInstallKubernetesDocsDescription": "For more and up to date Kubernetes installation information, see <docsLink>docs.pangolin.net/manage/sites/install-kubernetes</docsLink>.",
"siteInstallAdvantechDocsDescription": "For Advantech modem installation instructions, see <docsLink>docs.pangolin.net/manage/sites/install-advantech</docsLink>.",
"WgConfiguration": "Konfigurace WireGuard", "WgConfiguration": "Konfigurace WireGuard",
"WgConfigurationDescription": "K připojení k síti použijte následující konfiguraci", "WgConfigurationDescription": "K připojení k síti použijte následující konfiguraci",
"operatingSystem": "Operační systém", "operatingSystem": "Operační systém",
@@ -202,8 +200,8 @@
"shareErrorSelectResource": "Zvolte prosím zdroj", "shareErrorSelectResource": "Zvolte prosím zdroj",
"proxyResourceTitle": "Spravovat veřejné zdroje", "proxyResourceTitle": "Spravovat veřejné zdroje",
"proxyResourceDescription": "Vytváření a správa zdrojů, které jsou veřejně přístupné prostřednictvím webového prohlížeče", "proxyResourceDescription": "Vytváření a správa zdrojů, které jsou veřejně přístupné prostřednictvím webového prohlížeče",
"publicResourcesBannerTitle": "Web-based Public Access", "publicResourcesBannerTitle": "Veřejný přístup založený na webu",
"publicResourcesBannerDescription": "Public resources are HTTPS proxies accessible to anyone on the internet through a web browser. Unlike private resources, they do not require client-side software and can include identity and context-aware access policies.", "publicResourcesBannerDescription": "Veřejné prostředky jsou HTTPS nebo TCP/UDP proxy, které jsou přístupné každému na internetu prostřednictvím webového prohlížeče. Na rozdíl od soukromých prostředků nevyžadují software na straně klienta a mohou zahrnovat politiky přístupu orientované na identitu a kontext.",
"clientResourceTitle": "Spravovat soukromé zdroje", "clientResourceTitle": "Spravovat soukromé zdroje",
"clientResourceDescription": "Vytváření a správa zdrojů, které jsou přístupné pouze prostřednictvím připojeného klienta", "clientResourceDescription": "Vytváření a správa zdrojů, které jsou přístupné pouze prostřednictvím připojeného klienta",
"privateResourcesBannerTitle": "Zero-Trust soukromý přístup", "privateResourcesBannerTitle": "Zero-Trust soukromý přístup",
@@ -211,16 +209,15 @@
"resourcesSearch": "Prohledat zdroje...", "resourcesSearch": "Prohledat zdroje...",
"resourceAdd": "Přidat zdroj", "resourceAdd": "Přidat zdroj",
"resourceErrorDelte": "Chyba při odstraňování zdroje", "resourceErrorDelte": "Chyba při odstraňování zdroje",
"resourcePoliciesTitle": "Manage Public Resource Policies", "resourcePoliciesTitle": "Spravovat zásady zdrojů",
"resourcePoliciesAttachedResourcesColumnTitle": "Resources", "resourcePoliciesAttachedResourcesColumnTitle": "Připojené zdroje",
"resourcePoliciesAttachedResources": "{count} zdroj(e/ů)", "resourcePoliciesAttachedResources": "{count} zdroj(e/ů)",
"resourcePoliciesAttachedResourcesCount": "{count, plural, one {# resource} other {# resources}}",
"resourcePoliciesAttachedResourcesEmpty": "žádné zdroje", "resourcePoliciesAttachedResourcesEmpty": "žádné zdroje",
"resourcePoliciesDescription": "Create and manage authentication policies to control access to your public resources", "resourcePoliciesDescription": "Vytvářejte a spravujte zásady ověřování k řízení přístupu ke svým zdrojům",
"resourcePoliciesSearch": "Hledat zásady...", "resourcePoliciesSearch": "Hledat zásady...",
"resourcePoliciesAdd": "Přidat zásadu", "resourcePoliciesAdd": "Přidat zásadu",
"resourcePoliciesDefaultBadgeText": "Výchozí zásada", "resourcePoliciesDefaultBadgeText": "Výchozí zásada",
"resourcePoliciesCreate": "Create Public Resource Policy", "resourcePoliciesCreate": "Vytvořit zásadu zdroje",
"resourcePoliciesCreateDescription": "Postupujte podle následujících kroků k vytvoření nové zásady", "resourcePoliciesCreateDescription": "Postupujte podle následujících kroků k vytvoření nové zásady",
"resourcePolicyName": "Název zásady", "resourcePolicyName": "Název zásady",
"resourcePolicyNameDescription": "Pojmenujte tuto zásadu, aby byla rozpoznatelná napříč vašimi zdroji", "resourcePolicyNameDescription": "Pojmenujte tuto zásadu, aby byla rozpoznatelná napříč vašimi zdroji",
@@ -314,7 +311,7 @@
"rules": "Pravidla", "rules": "Pravidla",
"resourceSettingDescription": "Konfigurace nastavení na zdroji", "resourceSettingDescription": "Konfigurace nastavení na zdroji",
"resourceSetting": "Nastavení {resourceName}", "resourceSetting": "Nastavení {resourceName}",
"resourcePolicySettingDescription": "Configure the settings on this public resource policy", "resourcePolicySettingDescription": "Nakonfigurujte nastavení na zásadě zdroje",
"resourcePolicySetting": "Nastavení {policyName}", "resourcePolicySetting": "Nastavení {policyName}",
"alwaysAllow": "Obejít Auth", "alwaysAllow": "Obejít Auth",
"alwaysDeny": "Blokovat přístup", "alwaysDeny": "Blokovat přístup",
@@ -756,11 +753,11 @@
"rulesErrorDuplicate": "Duplikovat pravidlo", "rulesErrorDuplicate": "Duplikovat pravidlo",
"rulesErrorDuplicateDescription": "Pravidlo s těmito nastaveními již existuje", "rulesErrorDuplicateDescription": "Pravidlo s těmito nastaveními již existuje",
"rulesErrorInvalidIpAddressRange": "Neplatný CIDR", "rulesErrorInvalidIpAddressRange": "Neplatný CIDR",
"rulesErrorInvalidIpAddressRangeDescription": "Enter a valid CIDR range (e.g., 10.0.0.0/8).", "rulesErrorInvalidIpAddressRangeDescription": "Zadejte prosím platnou hodnotu CIDR",
"rulesErrorInvalidUrl": "Invalid path", "rulesErrorInvalidUrl": "Neplatná URL cesta",
"rulesErrorInvalidUrlDescription": "Enter a valid URL path or pattern (e.g., /api/*).", "rulesErrorInvalidUrlDescription": "Zadejte platnou hodnotu URL cesty",
"rulesErrorInvalidIpAddress": "Invalid IP address", "rulesErrorInvalidIpAddress": "Neplatná IP adresa",
"rulesErrorInvalidIpAddressDescription": "Enter a valid IPv4 or IPv6 address.", "rulesErrorInvalidIpAddressDescription": "Zadejte prosím platnou IP adresu",
"rulesErrorUpdate": "Aktualizace pravidel se nezdařila", "rulesErrorUpdate": "Aktualizace pravidel se nezdařila",
"rulesErrorUpdateDescription": "Při aktualizaci pravidel došlo k chybě", "rulesErrorUpdateDescription": "Při aktualizaci pravidel došlo k chybě",
"rulesUpdated": "Povolit pravidla", "rulesUpdated": "Povolit pravidla",
@@ -768,23 +765,15 @@
"rulesMatchIpAddressRangeDescription": "Zadejte adresu ve formátu CIDR (např. 103.21.244.0/22)", "rulesMatchIpAddressRangeDescription": "Zadejte adresu ve formátu CIDR (např. 103.21.244.0/22)",
"rulesMatchIpAddress": "Zadejte IP adresu (např. 103.21.244.12)", "rulesMatchIpAddress": "Zadejte IP adresu (např. 103.21.244.12)",
"rulesMatchUrl": "Zadejte URL cestu nebo vzor (např. /api/v1/todos nebo /api/v1/*)", "rulesMatchUrl": "Zadejte URL cestu nebo vzor (např. /api/v1/todos nebo /api/v1/*)",
"rulesErrorInvalidPriority": "Invalid priority", "rulesErrorInvalidPriority": "Neplatná Priorita",
"rulesErrorInvalidPriorityDescription": "Enter a whole number of 1 or higher.", "rulesErrorInvalidPriorityDescription": "Zadejte prosím platnou prioritu",
"rulesErrorDuplicatePriority": "Duplicate priorities", "rulesErrorDuplicatePriority": "Duplikovat priority",
"rulesErrorDuplicatePriorityDescription": "Each rule must have a unique priority number.", "rulesErrorDuplicatePriorityDescription": "Zadejte prosím unikátní priority",
"rulesErrorValidation": "Invalid rules",
"rulesErrorValidationRuleDescription": "Rule {ruleNumber}: {message}",
"rulesErrorValueRequired": "Enter a value for this rule.",
"rulesErrorInvalidCountry": "Invalid country",
"rulesErrorInvalidCountryDescription": "Select a valid country.",
"rulesErrorInvalidAsn": "Invalid ASN",
"rulesErrorInvalidAsnDescription": "Enter a valid ASN (e.g., AS15169).",
"ruleUpdated": "Pravidla byla aktualizována", "ruleUpdated": "Pravidla byla aktualizována",
"ruleUpdatedDescription": "Pravidla byla úspěšně aktualizována", "ruleUpdatedDescription": "Pravidla byla úspěšně aktualizována",
"ruleErrorUpdate": "Operace selhala", "ruleErrorUpdate": "Operace selhala",
"ruleErrorUpdateDescription": "Při ukládání došlo k chybě", "ruleErrorUpdateDescription": "Při ukládání došlo k chybě",
"rulesPriority": "Priorita", "rulesPriority": "Priorita",
"rulesReorderDragHandle": "Drag to reorder rule priority",
"rulesAction": "Akce", "rulesAction": "Akce",
"rulesMatchType": "Typ shody", "rulesMatchType": "Typ shody",
"value": "Hodnota", "value": "Hodnota",
@@ -803,7 +792,7 @@
"rulesResource": "Konfigurace pravidel zdroje", "rulesResource": "Konfigurace pravidel zdroje",
"rulesResourceDescription": "Nastavit pravidla pro kontrolu přístupu ke zdroji", "rulesResourceDescription": "Nastavit pravidla pro kontrolu přístupu ke zdroji",
"ruleSubmit": "Přidat pravidlo", "ruleSubmit": "Přidat pravidlo",
"rulesNoOne": "No rules yet.", "rulesNoOne": "Žádná pravidla. Přidejte pravidlo pomocí formuláře.",
"rulesOrder": "Pravidla jsou hodnocena podle priority vzestupně.", "rulesOrder": "Pravidla jsou hodnocena podle priority vzestupně.",
"rulesSubmit": "Uložit pravidla", "rulesSubmit": "Uložit pravidla",
"policyErrorCreate": "Chyba při vytváření zásady", "policyErrorCreate": "Chyba při vytváření zásady",
@@ -814,44 +803,7 @@
"policyErrorUpdateMessageDescription": "Došlo k neočekávané chybě", "policyErrorUpdateMessageDescription": "Došlo k neočekávané chybě",
"policyCreatedSuccess": "Zásada zdroje byla úspěšně vytvořena", "policyCreatedSuccess": "Zásada zdroje byla úspěšně vytvořena",
"policyUpdatedSuccess": "Zásada zdroje byla úspěšně aktualizována", "policyUpdatedSuccess": "Zásada zdroje byla úspěšně aktualizována",
"authMethodsSave": "Save Settings", "authMethodsSave": "Uložit metody ověřování",
"policyAuthStackTitle": "Authentication",
"policyAuthStackDescription": "Control which authentication methods are required to access this resource",
"policyAuthOrLogicTitle": "Multiple authentication methods active",
"policyAuthOrLogicBanner": "Visitors may authenticate using any one of the active methods below. They do not need to complete all of them.",
"policyAuthMethodActive": "Active",
"policyAuthMethodOff": "Off",
"policyAuthSsoTitle": "Platform SSO",
"policyAuthSsoDescription": "Require sign-in through your organization's identity provider",
"policyAuthSsoSummary": "{idp} · {users} users, {roles} roles",
"policyAuthSsoDefaultIdp": "Default provider",
"policyAuthAddDefaultIdentityProvider": "Add Default Identity Provider",
"policyAuthOtherMethodsTitle": "Other Methods",
"policyAuthOtherMethodsDescription": "Optional methods visitors can use instead of or alongside platform SSO",
"policyAuthPasscodeTitle": "Passcode",
"policyAuthPasscodeDescription": "Require a shared alphanumeric passcode to access the resource",
"policyAuthPasscodeSummary": "Passcode set",
"policyAuthPincodeTitle": "PIN Code",
"policyAuthPincodeDescription": "A short numeric code required to access the resource",
"policyAuthPincodeSummary": "6-digit PIN set",
"policyAuthEmailTitle": "Email Whitelist",
"policyAuthEmailDescription": "Allow listed email addresses with one-time passwords",
"policyAuthEmailSummary": "{count} addresses allowed",
"policyAuthEmailOtpCallout": "Enabling email whitelist sends a one-time password to the visitor's email on login.",
"policyAuthHeaderAuthTitle": "Basic Header Auth",
"policyAuthHeaderAuthDescription": "Validate a custom HTTP header name and value on each request",
"policyAuthHeaderAuthSummary": "Header configured",
"policyAuthHeaderName": "Header name",
"policyAuthHeaderValue": "Expected value",
"policyAccessRulesTitle": "Access Rules",
"policyAccessRulesEnableDescription": "When enabled, rules are evaluated in descending order until one evaluates as true.",
"policyAccessRulesFirstMatch": "Rules are evaluated top to bottom. The first matching rule decides the outcome.",
"policyAccessRulesHowItWorks": "Rules match requests by path, IP address, location, or other criteria. Each rule applies an action: bypass authentication, block access, or pass to authentication. If no rule matches, traffic continues to authentication.",
"policyAccessRulesFallthroughOff": "When rules are disabled, all traffic passes through to authentication.",
"policyAccessRulesFallthroughOn": "When no rule matches, traffic passes through to authentication.",
"rulesPlaceholderCidr": "10.0.0.0/8",
"rulesPlaceholderPath": "/admin/*",
"rulesPlaceholderGeo": "RU, KP",
"rulesSave": "Uložit pravidla", "rulesSave": "Uložit pravidla",
"resourceErrorCreate": "Chyba při vytváření zdroje", "resourceErrorCreate": "Chyba při vytváření zdroje",
"resourceErrorCreateDescription": "Při vytváření zdroje došlo k chybě", "resourceErrorCreateDescription": "Při vytváření zdroje došlo k chybě",
@@ -1268,10 +1220,8 @@
"addLabels": "Přidat štítky", "addLabels": "Přidat štítky",
"siteLabelsTab": "Štítky", "siteLabelsTab": "Štítky",
"siteLabelsDescription": "Spravujte štítky přiřazené k této lokalitě.", "siteLabelsDescription": "Spravujte štítky přiřazené k této lokalitě.",
"labelsNotFound": "No labels found.", "labelsNotFound": "Štítky nenalezeny",
"labelsEmptyCreateHint": "Start typing above to create a label.",
"labelSearch": "Hledat štítky", "labelSearch": "Hledat štítky",
"labelSearchOrCreate": "Search or create a label",
"accessLabelFilterCount": "{count, plural, one {# štítek} few {# štítky} other {# štítků}}", "accessLabelFilterCount": "{count, plural, one {# štítek} few {# štítky} other {# štítků}}",
"labelOverflowCount": "+{count, plural, one {# štítek} few {# štítky} other {# štítků}}", "labelOverflowCount": "+{count, plural, one {# štítek} few {# štítky} other {# štítků}}",
"accessLabelFilterClear": "Vymazat filtry štítků", "accessLabelFilterClear": "Vymazat filtry štítků",
@@ -1512,7 +1462,7 @@
"sidebarProxyResources": "Veřejnost", "sidebarProxyResources": "Veřejnost",
"sidebarClientResources": "Soukromé", "sidebarClientResources": "Soukromé",
"sidebarPolicies": "Zásady", "sidebarPolicies": "Zásady",
"sidebarResourcePolicies": "Public Resources", "sidebarResourcePolicies": "Zdroje",
"sidebarAccessControl": "Kontrola přístupu", "sidebarAccessControl": "Kontrola přístupu",
"sidebarLogsAndAnalytics": "Logy & Analytika", "sidebarLogsAndAnalytics": "Logy & Analytika",
"sidebarTeam": "Tým", "sidebarTeam": "Tým",
@@ -1520,7 +1470,7 @@
"sidebarAdmin": "Admin", "sidebarAdmin": "Admin",
"sidebarInvitations": "Pozvánky", "sidebarInvitations": "Pozvánky",
"sidebarRoles": "Role", "sidebarRoles": "Role",
"sidebarShareableLinks": "Share Links", "sidebarShareableLinks": "Odkazy",
"sidebarApiKeys": "API klíče", "sidebarApiKeys": "API klíče",
"sidebarProvisioning": "Zajištění", "sidebarProvisioning": "Zajištění",
"sidebarSettings": "Nastavení", "sidebarSettings": "Nastavení",
@@ -1697,7 +1647,7 @@
"standaloneHcFilterResourceIdFallback": "Zdroj {id}", "standaloneHcFilterResourceIdFallback": "Zdroj {id}",
"blueprints": "Plány", "blueprints": "Plány",
"blueprintsLog": "Protokol plánů", "blueprintsLog": "Protokol plánů",
"blueprintsDescription": "View past blueprint applications and their results or apply a new blueprint", "blueprintsDescription": "Prohlédněte si aplikace předchozích plánů a jejich výsledky",
"blueprintAdd": "Přidat plán", "blueprintAdd": "Přidat plán",
"blueprintGoBack": "Zobrazit všechny plány", "blueprintGoBack": "Zobrazit všechny plány",
"blueprintCreate": "Vytvořit plán", "blueprintCreate": "Vytvořit plán",
@@ -2077,13 +2027,13 @@
"healthCheckUnknown": "Neznámý", "healthCheckUnknown": "Neznámý",
"healthCheck": "Kontrola stavu", "healthCheck": "Kontrola stavu",
"configureHealthCheck": "Konfigurace kontroly stavu", "configureHealthCheck": "Konfigurace kontroly stavu",
"configureHealthCheckDescription": "Set up monitoring for your resource to ensure it is always available", "configureHealthCheckDescription": "Nastavit sledování zdravotního stavu pro {target}",
"enableHealthChecks": "Povolit kontrolu stavu", "enableHealthChecks": "Povolit kontrolu stavu",
"healthCheckDisabledStateDescription": "Pokud je zakázáno, web nebude provádět zdravotní kontroly a stav bude považován za neznámý.", "healthCheckDisabledStateDescription": "Pokud je zakázáno, web nebude provádět zdravotní kontroly a stav bude považován za neznámý.",
"enableHealthChecksDescription": "Sledujte zdraví tohoto cíle. V případě potřeby můžete sledovat jiný cílový bod, než je cíl.", "enableHealthChecksDescription": "Sledujte zdraví tohoto cíle. V případě potřeby můžete sledovat jiný cílový bod, než je cíl.",
"healthScheme": "Způsob", "healthScheme": "Způsob",
"healthSelectScheme": "Vybrat metodu", "healthSelectScheme": "Vybrat metodu",
"healthCheckPortInvalid": "Port must be between 1 and 65535", "healthCheckPortInvalid": "Přístav kontroly stavu musí být mezi 1 a 65535",
"healthCheckPath": "Cesta", "healthCheckPath": "Cesta",
"healthHostname": "IP / Hostitel", "healthHostname": "IP / Hostitel",
"healthPort": "Přístav", "healthPort": "Přístav",
@@ -2096,7 +2046,6 @@
"requireDeviceApproval": "Vyžadovat schválení zařízení", "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.", "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.",
"sshSettings": "Nastavení SSH", "sshSettings": "Nastavení SSH",
"sshAccess": "SSH Access",
"rdpSettings": "Nastavení RDP", "rdpSettings": "Nastavení RDP",
"vncSettings": "Nastavení VNC", "vncSettings": "Nastavení VNC",
"sshServer": "SSH server", "sshServer": "SSH server",
@@ -2123,13 +2072,8 @@
"sshDaemonDisclaimer": "Ujistěte se, že váš cílový hostitel je správně nakonfigurován k přímu spuštění ověřovacího démona, jinak zřizování selže.", "sshDaemonDisclaimer": "Ujistěte se, že váš cílový hostitel je správně nakonfigurován k přímu spuštění ověřovacího démona, jinak zřizování selže.",
"sshDaemonPort": "Port démona", "sshDaemonPort": "Port démona",
"sshServerDestination": "Cíl serveru", "sshServerDestination": "Cíl serveru",
"sshServerDestinationDescription": "Configure the destination of the SSH server", "sshServerDestinationDescription": "Nakonfigurujte cíl a port SSH serveru",
"destination": "Cíl", "destination": "Cíl",
"destinationRequired": "Destination is required.",
"domainRequired": "Domain is required.",
"proxyPortRequired": "Port is required.",
"invalidPathConfiguration": "Invalid path configuration.",
"invalidRewritePathConfiguration": "Invalid rewrite path configuration.",
"bgTargetMultiSiteDisclaimer": "Výběr více lokalit umožňuje odolné směrování a převzetí služeb při selhání pro vysokou dostupnost.", "bgTargetMultiSiteDisclaimer": "Výběr více lokalit umožňuje odolné směrování a převzetí služeb při selhání pro vysokou dostupnost.",
"roleAllowSsh": "Povolit SSH", "roleAllowSsh": "Povolit SSH",
"roleAllowSshAllow": "Povolit", "roleAllowSshAllow": "Povolit",
@@ -3090,7 +3034,7 @@
"enterConfirmation": "Zadejte potvrzení", "enterConfirmation": "Zadejte potvrzení",
"blueprintViewDetails": "Detaily", "blueprintViewDetails": "Detaily",
"defaultIdentityProvider": "Výchozí poskytovatel identity", "defaultIdentityProvider": "Výchozí poskytovatel identity",
"defaultIdentityProviderDescription": "The user will be automatically redirected to this identity provider for authentication.", "defaultIdentityProviderDescription": "Pokud je vybrán výchozí poskytovatel identity, uživatel bude automaticky přesměrován na poskytovatele pro ověření.",
"editInternalResourceDialogNetworkSettings": "Nastavení sítě", "editInternalResourceDialogNetworkSettings": "Nastavení sítě",
"editInternalResourceDialogAccessPolicy": "Přístupová politika", "editInternalResourceDialogAccessPolicy": "Přístupová politika",
"editInternalResourceDialogAddRoles": "Přidat role", "editInternalResourceDialogAddRoles": "Přidat role",
@@ -3402,8 +3346,6 @@
"idpUnassociateQuestion": "Opravdu chcete odpojit tohoto poskytovatele identity od této organizace?", "idpUnassociateQuestion": "Opravdu chcete odpojit tohoto poskytovatele identity od této organizace?",
"idpUnassociateDescription": "Všichni uživatelé spojení s tímto poskytovatelem identity budou odstraněni z této organizace, ale poskytovatel identity zůstane nadále existovat pro ostatní přidružené organizace.", "idpUnassociateDescription": "Všichni uživatelé spojení s tímto poskytovatelem identity budou odstraněni z této organizace, ale poskytovatel identity zůstane nadále existovat pro ostatní přidružené organizace.",
"idpUnassociateConfirm": "Potvrdit odpojení poskytovatele identity", "idpUnassociateConfirm": "Potvrdit odpojení poskytovatele identity",
"idpConfirmDeleteAndRemoveMeFromOrg": "DELETE AND REMOVE ME FROM ORG",
"idpUnassociateAndRemoveMeFromOrg": "UNASSOCIATE AND REMOVE ME FROM ORG",
"idpUnassociateWarning": "Toto nelze pro tuto organizaci vrátit.", "idpUnassociateWarning": "Toto nelze pro tuto organizaci vrátit.",
"idpUnassociatedDescription": "Poskytovatel identity byl úspěšně odpojen od této organizace", "idpUnassociatedDescription": "Poskytovatel identity byl úspěšně odpojen od této organizace",
"idpUnassociateMenu": "Odpojit", "idpUnassociateMenu": "Odpojit",
@@ -3510,43 +3452,5 @@
"sshErrorNoTarget": "Cíl nebyl určen", "sshErrorNoTarget": "Cíl nebyl určen",
"sshErrorWebSocket": "Chyba připojení WebSocketu", "sshErrorWebSocket": "Chyba připojení WebSocketu",
"sshErrorAuthFailed": "Ověření selhalo", "sshErrorAuthFailed": "Ověření selhalo",
"sshErrorConnectionClosed": "Připojení bylo uzavřeno před dokončením ověřování", "sshErrorConnectionClosed": "Připojení bylo uzavřeno před dokončením ověřování"
"sitePangolinSshDescription": "Allow SSH access to resources on this site. This can be changed later.",
"browserGatewayNoResourceForDomain": "No resource found for this domain",
"browserGatewayNoTarget": "No target",
"browserGatewayConnect": "Connect",
"browserGatewayCtrlAltDel": "Ctrl+Alt+Del",
"sshErrorSignKeyFailed": "Failed to sign SSH key for PAM push authentication. Did you sign in as a user?",
"sshTerminalError": "Error: {error}",
"sshConnectionClosedCode": "Connection closed (code {code})",
"sshPrivateKeyPlaceholder": "-----BEGIN OPENSSH PRIVATE KEY-----",
"sshPrivateKeyRequired": "Private key is required",
"vncTitle": "VNC",
"vncSignInDescription": "Enter your VNC password to connect",
"vncPasswordOptional": "Password (optional)",
"vncNoResourceTarget": "No resource target is available",
"vncFailedToLoadNovnc": "Failed to load noVNC",
"vncAuthFailedStatus": "Status {status}",
"vncPasteClipboard": "Paste clipboard",
"rdpTitle": "RDP",
"rdpSignInTitle": "Sign in to Remote Desktop",
"rdpSignInDescription": "Enter Windows credentials to connect",
"rdpLoadingModule": "Loading module...",
"rdpFailedToLoadModule": "Failed to load RDP module",
"rdpNotReady": "Not ready",
"rdpModuleInitializing": "RDP module is still initializing",
"rdpDownloadingFiles": "Downloading {count} file(s) from remote…",
"rdpDownloadFailed": "Download failed: {fileName}",
"rdpUploaded": "Uploaded: {fileName}",
"rdpNoConnectionTarget": "No connection target available",
"rdpConnectionFailed": "Connection failed",
"rdpFit": "Fit",
"rdpFull": "Full",
"rdpReal": "Real",
"rdpMeta": "Meta",
"rdpUploadFiles": "Upload files",
"rdpFilesReadyToPaste": "Files ready to paste",
"rdpFilesReadyToPasteDescription": "{count} file(s) copied to remote clipboard — press Ctrl+V on the remote desktop to paste.",
"rdpUploadFailed": "Upload failed",
"rdpUnicodeKeyboardMode": "Unicode keyboard mode"
} }
+27 -123
View File
@@ -101,8 +101,6 @@
"sitesTableViewPrivateResources": "Private Ressourcen anzeigen", "sitesTableViewPrivateResources": "Private Ressourcen anzeigen",
"siteInstallNewt": "Newt installieren", "siteInstallNewt": "Newt installieren",
"siteInstallNewtDescription": "Installiere Newt auf deinem System.", "siteInstallNewtDescription": "Installiere Newt auf deinem System.",
"siteInstallKubernetesDocsDescription": "For more and up to date Kubernetes installation information, see <docsLink>docs.pangolin.net/manage/sites/install-kubernetes</docsLink>.",
"siteInstallAdvantechDocsDescription": "For Advantech modem installation instructions, see <docsLink>docs.pangolin.net/manage/sites/install-advantech</docsLink>.",
"WgConfiguration": "WireGuard Konfiguration", "WgConfiguration": "WireGuard Konfiguration",
"WgConfigurationDescription": "Verwenden Sie folgende Konfiguration, um sich mit dem Netzwerk zu verbinden", "WgConfigurationDescription": "Verwenden Sie folgende Konfiguration, um sich mit dem Netzwerk zu verbinden",
"operatingSystem": "Betriebssystem", "operatingSystem": "Betriebssystem",
@@ -202,8 +200,8 @@
"shareErrorSelectResource": "Bitte wählen Sie eine Ressource", "shareErrorSelectResource": "Bitte wählen Sie eine Ressource",
"proxyResourceTitle": "Öffentliche Ressourcen verwalten", "proxyResourceTitle": "Öffentliche Ressourcen verwalten",
"proxyResourceDescription": "Erstelle und verwalte Ressourcen, die über einen Webbrowser öffentlich zugänglich sind", "proxyResourceDescription": "Erstelle und verwalte Ressourcen, die über einen Webbrowser öffentlich zugänglich sind",
"publicResourcesBannerTitle": "Web-based Public Access", "publicResourcesBannerTitle": "Web-basierter öffentlicher Zugang",
"publicResourcesBannerDescription": "Public resources are HTTPS proxies accessible to anyone on the internet through a web browser. Unlike private resources, they do not require client-side software and can include identity and context-aware access policies.", "publicResourcesBannerDescription": "Öffentliche Ressourcen sind HTTPS oder TCP/UDP-Proxys, die über einen Webbrowser für jeden zugänglich sind. Im Gegensatz zu privaten Ressourcen benötigen sie keine Client-seitige Software und können Identitäts- und kontextbezogene Zugriffsrichtlinien beinhalten.",
"clientResourceTitle": "Private Ressourcen verwalten", "clientResourceTitle": "Private Ressourcen verwalten",
"clientResourceDescription": "Erstelle und verwalte Ressourcen, die nur über einen verbundenen Client zugänglich sind", "clientResourceDescription": "Erstelle und verwalte Ressourcen, die nur über einen verbundenen Client zugänglich sind",
"privateResourcesBannerTitle": "Zero-Trust-Zugriff auf private Ressourcen", "privateResourcesBannerTitle": "Zero-Trust-Zugriff auf private Ressourcen",
@@ -211,16 +209,15 @@
"resourcesSearch": "Suche Ressourcen...", "resourcesSearch": "Suche Ressourcen...",
"resourceAdd": "Ressource hinzufügen", "resourceAdd": "Ressource hinzufügen",
"resourceErrorDelte": "Fehler beim Löschen der Ressource", "resourceErrorDelte": "Fehler beim Löschen der Ressource",
"resourcePoliciesTitle": "Manage Public Resource Policies", "resourcePoliciesTitle": "Ressourcenrichtlinien verwalten",
"resourcePoliciesAttachedResourcesColumnTitle": "Resources", "resourcePoliciesAttachedResourcesColumnTitle": "Angehängte Ressourcen",
"resourcePoliciesAttachedResources": "{count} Ressource(n)", "resourcePoliciesAttachedResources": "{count} Ressource(n)",
"resourcePoliciesAttachedResourcesCount": "{count, plural, one {# resource} other {# resources}}",
"resourcePoliciesAttachedResourcesEmpty": "keine Ressourcen", "resourcePoliciesAttachedResourcesEmpty": "keine Ressourcen",
"resourcePoliciesDescription": "Create and manage authentication policies to control access to your public resources", "resourcePoliciesDescription": "Erstellen und verwalten Sie Authentifizierungsrichtlinien, um den Zugang zu Ihren Ressourcen zu steuern",
"resourcePoliciesSearch": "Richtlinien suchen...", "resourcePoliciesSearch": "Richtlinien suchen...",
"resourcePoliciesAdd": "Richtlinie hinzufügen", "resourcePoliciesAdd": "Richtlinie hinzufügen",
"resourcePoliciesDefaultBadgeText": "Standardrichtlinie", "resourcePoliciesDefaultBadgeText": "Standardrichtlinie",
"resourcePoliciesCreate": "Create Public Resource Policy", "resourcePoliciesCreate": "Ressourcenrichtlinie erstellen",
"resourcePoliciesCreateDescription": "Befolgen Sie die unten stehenden Schritte, um eine neue Richtlinie zu erstellen", "resourcePoliciesCreateDescription": "Befolgen Sie die unten stehenden Schritte, um eine neue Richtlinie zu erstellen",
"resourcePolicyName": "Richtlinienname", "resourcePolicyName": "Richtlinienname",
"resourcePolicyNameDescription": "Geben Sie dieser Richtlinie einen Namen, um sie für Ihre Ressourcen zu identifizieren", "resourcePolicyNameDescription": "Geben Sie dieser Richtlinie einen Namen, um sie für Ihre Ressourcen zu identifizieren",
@@ -314,7 +311,7 @@
"rules": "Regeln", "rules": "Regeln",
"resourceSettingDescription": "Einstellungen für die Ressource konfigurieren", "resourceSettingDescription": "Einstellungen für die Ressource konfigurieren",
"resourceSetting": "{resourceName} Einstellungen", "resourceSetting": "{resourceName} Einstellungen",
"resourcePolicySettingDescription": "Configure the settings on this public resource policy", "resourcePolicySettingDescription": "Konfigurieren Sie die Einstellungen der Ressourcenrichtlinie",
"resourcePolicySetting": "{policyName} Einstellungen", "resourcePolicySetting": "{policyName} Einstellungen",
"alwaysAllow": "Authentifizierung umgehen", "alwaysAllow": "Authentifizierung umgehen",
"alwaysDeny": "Zugriff blockieren", "alwaysDeny": "Zugriff blockieren",
@@ -756,11 +753,11 @@
"rulesErrorDuplicate": "Doppelte Regel", "rulesErrorDuplicate": "Doppelte Regel",
"rulesErrorDuplicateDescription": "Eine Regel mit diesen Einstellungen existiert bereits", "rulesErrorDuplicateDescription": "Eine Regel mit diesen Einstellungen existiert bereits",
"rulesErrorInvalidIpAddressRange": "Ungültiger CIDR", "rulesErrorInvalidIpAddressRange": "Ungültiger CIDR",
"rulesErrorInvalidIpAddressRangeDescription": "Enter a valid CIDR range (e.g., 10.0.0.0/8).", "rulesErrorInvalidIpAddressRangeDescription": "Bitte geben Sie einen gültigen CIDR-Wert ein",
"rulesErrorInvalidUrl": "Invalid path", "rulesErrorInvalidUrl": "Ungültiger URL-Pfad",
"rulesErrorInvalidUrlDescription": "Enter a valid URL path or pattern (e.g., /api/*).", "rulesErrorInvalidUrlDescription": "Bitte geben Sie einen gültigen URL-Pfad-Wert ein",
"rulesErrorInvalidIpAddress": "Invalid IP address", "rulesErrorInvalidIpAddress": "Ungültige IP",
"rulesErrorInvalidIpAddressDescription": "Enter a valid IPv4 or IPv6 address.", "rulesErrorInvalidIpAddressDescription": "Bitte geben Sie eine gültige IP-Adresse ein",
"rulesErrorUpdate": "Fehler beim Aktualisieren der Regeln", "rulesErrorUpdate": "Fehler beim Aktualisieren der Regeln",
"rulesErrorUpdateDescription": "Beim Aktualisieren der Regeln ist ein Fehler aufgetreten", "rulesErrorUpdateDescription": "Beim Aktualisieren der Regeln ist ein Fehler aufgetreten",
"rulesUpdated": "Regeln aktivieren", "rulesUpdated": "Regeln aktivieren",
@@ -768,23 +765,15 @@
"rulesMatchIpAddressRangeDescription": "Geben Sie eine Adresse im CIDR-Format ein (z.B. 103.21.244.0/22)", "rulesMatchIpAddressRangeDescription": "Geben Sie eine Adresse im CIDR-Format ein (z.B. 103.21.244.0/22)",
"rulesMatchIpAddress": "Geben Sie eine IP-Adresse ein (z.B. 103.21.244.12)", "rulesMatchIpAddress": "Geben Sie eine IP-Adresse ein (z.B. 103.21.244.12)",
"rulesMatchUrl": "Geben Sie einen URL-Pfad oder -Muster ein (z.B. /api/v1/todos oder /api/v1/*)", "rulesMatchUrl": "Geben Sie einen URL-Pfad oder -Muster ein (z.B. /api/v1/todos oder /api/v1/*)",
"rulesErrorInvalidPriority": "Invalid priority", "rulesErrorInvalidPriority": "Ungültige Priorität",
"rulesErrorInvalidPriorityDescription": "Enter a whole number of 1 or higher.", "rulesErrorInvalidPriorityDescription": "Bitte geben Sie eine gültige Priorität ein",
"rulesErrorDuplicatePriority": "Duplicate priorities", "rulesErrorDuplicatePriority": "Doppelte Prioritäten",
"rulesErrorDuplicatePriorityDescription": "Each rule must have a unique priority number.", "rulesErrorDuplicatePriorityDescription": "Bitte geben Sie eindeutige Prioritäten ein",
"rulesErrorValidation": "Invalid rules",
"rulesErrorValidationRuleDescription": "Rule {ruleNumber}: {message}",
"rulesErrorValueRequired": "Enter a value for this rule.",
"rulesErrorInvalidCountry": "Invalid country",
"rulesErrorInvalidCountryDescription": "Select a valid country.",
"rulesErrorInvalidAsn": "Invalid ASN",
"rulesErrorInvalidAsnDescription": "Enter a valid ASN (e.g., AS15169).",
"ruleUpdated": "Regeln aktualisiert", "ruleUpdated": "Regeln aktualisiert",
"ruleUpdatedDescription": "Regeln erfolgreich aktualisiert", "ruleUpdatedDescription": "Regeln erfolgreich aktualisiert",
"ruleErrorUpdate": "Operation fehlgeschlagen", "ruleErrorUpdate": "Operation fehlgeschlagen",
"ruleErrorUpdateDescription": "Während des Speichervorgangs ist ein Fehler aufgetreten", "ruleErrorUpdateDescription": "Während des Speichervorgangs ist ein Fehler aufgetreten",
"rulesPriority": "Priorität", "rulesPriority": "Priorität",
"rulesReorderDragHandle": "Drag to reorder rule priority",
"rulesAction": "Aktion", "rulesAction": "Aktion",
"rulesMatchType": "Übereinstimmungstyp", "rulesMatchType": "Übereinstimmungstyp",
"value": "Wert", "value": "Wert",
@@ -803,7 +792,7 @@
"rulesResource": "Ressourcen-Regelkonfiguration", "rulesResource": "Ressourcen-Regelkonfiguration",
"rulesResourceDescription": "Regeln konfigurieren, um den Zugriff auf die Ressource zu steuern", "rulesResourceDescription": "Regeln konfigurieren, um den Zugriff auf die Ressource zu steuern",
"ruleSubmit": "Regel hinzufügen", "ruleSubmit": "Regel hinzufügen",
"rulesNoOne": "No rules yet.", "rulesNoOne": "Keine Regeln. Fügen Sie eine Regel über das Formular hinzu.",
"rulesOrder": "Regeln werden nach aufsteigender Priorität ausgewertet.", "rulesOrder": "Regeln werden nach aufsteigender Priorität ausgewertet.",
"rulesSubmit": "Regeln speichern", "rulesSubmit": "Regeln speichern",
"policyErrorCreate": "Fehler beim Erstellen der Richtlinie", "policyErrorCreate": "Fehler beim Erstellen der Richtlinie",
@@ -814,44 +803,7 @@
"policyErrorUpdateMessageDescription": "Ein unerwarteter Fehler ist aufgetreten", "policyErrorUpdateMessageDescription": "Ein unerwarteter Fehler ist aufgetreten",
"policyCreatedSuccess": "Ressourcenrichtlinie erfolgreich erstellt", "policyCreatedSuccess": "Ressourcenrichtlinie erfolgreich erstellt",
"policyUpdatedSuccess": "Ressourcenrichtlinie erfolgreich aktualisiert", "policyUpdatedSuccess": "Ressourcenrichtlinie erfolgreich aktualisiert",
"authMethodsSave": "Save Settings", "authMethodsSave": "Authentifizierungsmethoden speichern",
"policyAuthStackTitle": "Authentication",
"policyAuthStackDescription": "Control which authentication methods are required to access this resource",
"policyAuthOrLogicTitle": "Multiple authentication methods active",
"policyAuthOrLogicBanner": "Visitors may authenticate using any one of the active methods below. They do not need to complete all of them.",
"policyAuthMethodActive": "Active",
"policyAuthMethodOff": "Off",
"policyAuthSsoTitle": "Platform SSO",
"policyAuthSsoDescription": "Require sign-in through your organization's identity provider",
"policyAuthSsoSummary": "{idp} · {users} users, {roles} roles",
"policyAuthSsoDefaultIdp": "Default provider",
"policyAuthAddDefaultIdentityProvider": "Add Default Identity Provider",
"policyAuthOtherMethodsTitle": "Other Methods",
"policyAuthOtherMethodsDescription": "Optional methods visitors can use instead of or alongside platform SSO",
"policyAuthPasscodeTitle": "Passcode",
"policyAuthPasscodeDescription": "Require a shared alphanumeric passcode to access the resource",
"policyAuthPasscodeSummary": "Passcode set",
"policyAuthPincodeTitle": "PIN Code",
"policyAuthPincodeDescription": "A short numeric code required to access the resource",
"policyAuthPincodeSummary": "6-digit PIN set",
"policyAuthEmailTitle": "Email Whitelist",
"policyAuthEmailDescription": "Allow listed email addresses with one-time passwords",
"policyAuthEmailSummary": "{count} addresses allowed",
"policyAuthEmailOtpCallout": "Enabling email whitelist sends a one-time password to the visitor's email on login.",
"policyAuthHeaderAuthTitle": "Basic Header Auth",
"policyAuthHeaderAuthDescription": "Validate a custom HTTP header name and value on each request",
"policyAuthHeaderAuthSummary": "Header configured",
"policyAuthHeaderName": "Header name",
"policyAuthHeaderValue": "Expected value",
"policyAccessRulesTitle": "Access Rules",
"policyAccessRulesEnableDescription": "When enabled, rules are evaluated in descending order until one evaluates as true.",
"policyAccessRulesFirstMatch": "Rules are evaluated top to bottom. The first matching rule decides the outcome.",
"policyAccessRulesHowItWorks": "Rules match requests by path, IP address, location, or other criteria. Each rule applies an action: bypass authentication, block access, or pass to authentication. If no rule matches, traffic continues to authentication.",
"policyAccessRulesFallthroughOff": "When rules are disabled, all traffic passes through to authentication.",
"policyAccessRulesFallthroughOn": "When no rule matches, traffic passes through to authentication.",
"rulesPlaceholderCidr": "10.0.0.0/8",
"rulesPlaceholderPath": "/admin/*",
"rulesPlaceholderGeo": "RU, KP",
"rulesSave": "Regeln speichern", "rulesSave": "Regeln speichern",
"resourceErrorCreate": "Fehler beim Erstellen der Ressource", "resourceErrorCreate": "Fehler beim Erstellen der Ressource",
"resourceErrorCreateDescription": "Beim Erstellen der Ressource ist ein Fehler aufgetreten", "resourceErrorCreateDescription": "Beim Erstellen der Ressource ist ein Fehler aufgetreten",
@@ -1268,10 +1220,8 @@
"addLabels": "Etiketten hinzufügen", "addLabels": "Etiketten hinzufügen",
"siteLabelsTab": "Etiketten", "siteLabelsTab": "Etiketten",
"siteLabelsDescription": "Verwalten Sie die mit diesem Standort verbundenen Etiketten.", "siteLabelsDescription": "Verwalten Sie die mit diesem Standort verbundenen Etiketten.",
"labelsNotFound": "No labels found.", "labelsNotFound": "Etiketten nicht gefunden",
"labelsEmptyCreateHint": "Start typing above to create a label.",
"labelSearch": "Etiketten suchen", "labelSearch": "Etiketten suchen",
"labelSearchOrCreate": "Search or create a label",
"accessLabelFilterCount": "{count, plural, one {# Etikett} other {# Etiketten}}", "accessLabelFilterCount": "{count, plural, one {# Etikett} other {# Etiketten}}",
"labelOverflowCount": "+{count, plural, one {# Etikett} other {# Etiketten}}", "labelOverflowCount": "+{count, plural, one {# Etikett} other {# Etiketten}}",
"accessLabelFilterClear": "Etikettenfilter löschen", "accessLabelFilterClear": "Etikettenfilter löschen",
@@ -1512,7 +1462,7 @@
"sidebarProxyResources": "Öffentlich", "sidebarProxyResources": "Öffentlich",
"sidebarClientResources": "Privat", "sidebarClientResources": "Privat",
"sidebarPolicies": "Richtlinien", "sidebarPolicies": "Richtlinien",
"sidebarResourcePolicies": "Public Resources", "sidebarResourcePolicies": "Ressourcen",
"sidebarAccessControl": "Zugriffskontrolle", "sidebarAccessControl": "Zugriffskontrolle",
"sidebarLogsAndAnalytics": "Protokolle & Analysen", "sidebarLogsAndAnalytics": "Protokolle & Analysen",
"sidebarTeam": "Team", "sidebarTeam": "Team",
@@ -1520,7 +1470,7 @@
"sidebarAdmin": "Admin", "sidebarAdmin": "Admin",
"sidebarInvitations": "Einladungen", "sidebarInvitations": "Einladungen",
"sidebarRoles": "Rollen", "sidebarRoles": "Rollen",
"sidebarShareableLinks": "Share Links", "sidebarShareableLinks": "Links",
"sidebarApiKeys": "API-Schlüssel", "sidebarApiKeys": "API-Schlüssel",
"sidebarProvisioning": "Bereitstellung", "sidebarProvisioning": "Bereitstellung",
"sidebarSettings": "Einstellungen", "sidebarSettings": "Einstellungen",
@@ -1697,7 +1647,7 @@
"standaloneHcFilterResourceIdFallback": "Ressource {id}", "standaloneHcFilterResourceIdFallback": "Ressource {id}",
"blueprints": "Blaupausen", "blueprints": "Blaupausen",
"blueprintsLog": "Blaupausen-Protokoll", "blueprintsLog": "Blaupausen-Protokoll",
"blueprintsDescription": "View past blueprint applications and their results or apply a new blueprint", "blueprintsDescription": "Frühere Blaupausen-Anwendungen und deren Ergebnisse ansehen",
"blueprintAdd": "Blueprint hinzufügen", "blueprintAdd": "Blueprint hinzufügen",
"blueprintGoBack": "Alle Blueprints ansehen", "blueprintGoBack": "Alle Blueprints ansehen",
"blueprintCreate": "Blueprint erstellen", "blueprintCreate": "Blueprint erstellen",
@@ -2077,13 +2027,13 @@
"healthCheckUnknown": "Unbekannt", "healthCheckUnknown": "Unbekannt",
"healthCheck": "Gesundheits-Check", "healthCheck": "Gesundheits-Check",
"configureHealthCheck": "Gesundheits-Check konfigurieren", "configureHealthCheck": "Gesundheits-Check konfigurieren",
"configureHealthCheckDescription": "Set up monitoring for your resource to ensure it is always available", "configureHealthCheckDescription": "Richten Sie die Gesundheitsüberwachung für {target} ein",
"enableHealthChecks": "Gesundheits-Checks aktivieren", "enableHealthChecks": "Gesundheits-Checks aktivieren",
"healthCheckDisabledStateDescription": "Wenn deaktiviert, führt der Standort keine Gesundheitsprüfungen durch und der Zustand wird als unbekannt betrachtet.", "healthCheckDisabledStateDescription": "Wenn deaktiviert, führt der Standort keine Gesundheitsprüfungen durch und der Zustand wird als unbekannt betrachtet.",
"enableHealthChecksDescription": "Überwachen Sie die Gesundheit dieses Ziels. Bei Bedarf können Sie einen anderen Endpunkt als das Ziel überwachen.", "enableHealthChecksDescription": "Überwachen Sie die Gesundheit dieses Ziels. Bei Bedarf können Sie einen anderen Endpunkt als das Ziel überwachen.",
"healthScheme": "Methode", "healthScheme": "Methode",
"healthSelectScheme": "Methode auswählen", "healthSelectScheme": "Methode auswählen",
"healthCheckPortInvalid": "Port must be between 1 and 65535", "healthCheckPortInvalid": "Der Gesundheitskontroll-Port muss zwischen 1 und 65535 liegen",
"healthCheckPath": "Pfad", "healthCheckPath": "Pfad",
"healthHostname": "IP / Host", "healthHostname": "IP / Host",
"healthPort": "Port", "healthPort": "Port",
@@ -2096,7 +2046,6 @@
"requireDeviceApproval": "Gerätegenehmigungen erforderlich", "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.", "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.",
"sshSettings": "SSH-Einstellungen", "sshSettings": "SSH-Einstellungen",
"sshAccess": "SSH Access",
"rdpSettings": "RDP-Einstellungen", "rdpSettings": "RDP-Einstellungen",
"vncSettings": "VNC-Einstellungen", "vncSettings": "VNC-Einstellungen",
"sshServer": "SSH-Server", "sshServer": "SSH-Server",
@@ -2123,13 +2072,8 @@
"sshDaemonDisclaimer": "Stellen Sie sicher, dass Ihr Zielhost korrekt konfiguriert ist, um den Auth-Daemon auszuführen, bevor Sie dieses Setup abschließen, andernfalls wird die Bereitstellung fehlschlagen.", "sshDaemonDisclaimer": "Stellen Sie sicher, dass Ihr Zielhost korrekt konfiguriert ist, um den Auth-Daemon auszuführen, bevor Sie dieses Setup abschließen, andernfalls wird die Bereitstellung fehlschlagen.",
"sshDaemonPort": "Daemon-Port", "sshDaemonPort": "Daemon-Port",
"sshServerDestination": "Serverziel", "sshServerDestination": "Serverziel",
"sshServerDestinationDescription": "Configure the destination of the SSH server", "sshServerDestinationDescription": "Konfigurieren Sie das Ziel und den Port des SSH-Servers",
"destination": "Ziel", "destination": "Ziel",
"destinationRequired": "Destination is required.",
"domainRequired": "Domain is required.",
"proxyPortRequired": "Port is required.",
"invalidPathConfiguration": "Invalid path configuration.",
"invalidRewritePathConfiguration": "Invalid rewrite path configuration.",
"bgTargetMultiSiteDisclaimer": "Die Auswahl mehrerer Standorte ermöglicht eine widerstandsfähige Weiterleitung und einen Failover für hohe Verfügbarkeit.", "bgTargetMultiSiteDisclaimer": "Die Auswahl mehrerer Standorte ermöglicht eine widerstandsfähige Weiterleitung und einen Failover für hohe Verfügbarkeit.",
"roleAllowSsh": "SSH erlauben", "roleAllowSsh": "SSH erlauben",
"roleAllowSshAllow": "Erlauben", "roleAllowSshAllow": "Erlauben",
@@ -3090,7 +3034,7 @@
"enterConfirmation": "Bestätigung eingeben", "enterConfirmation": "Bestätigung eingeben",
"blueprintViewDetails": "Details", "blueprintViewDetails": "Details",
"defaultIdentityProvider": "Standard Identitätsanbieter", "defaultIdentityProvider": "Standard Identitätsanbieter",
"defaultIdentityProviderDescription": "The user will be automatically redirected to this identity provider for authentication.", "defaultIdentityProviderDescription": "Wenn ein Standard-Identity Provider ausgewählt ist, wird der Benutzer zur Authentifizierung automatisch an den Anbieter weitergeleitet.",
"editInternalResourceDialogNetworkSettings": "Netzwerkeinstellungen", "editInternalResourceDialogNetworkSettings": "Netzwerkeinstellungen",
"editInternalResourceDialogAccessPolicy": "Zugriffsrichtlinie", "editInternalResourceDialogAccessPolicy": "Zugriffsrichtlinie",
"editInternalResourceDialogAddRoles": "Rollen hinzufügen", "editInternalResourceDialogAddRoles": "Rollen hinzufügen",
@@ -3402,8 +3346,6 @@
"idpUnassociateQuestion": "Sind Sie sicher, dass Sie die Verknüpfung dieses Identitätsanbieters mit dieser Organisation aufheben möchten?", "idpUnassociateQuestion": "Sind Sie sicher, dass Sie die Verknüpfung dieses Identitätsanbieters mit dieser Organisation aufheben möchten?",
"idpUnassociateDescription": "Alle Benutzer, die mit diesem Identitätsanbieter verbunden sind, werden aus dieser Organisation entfernt, aber der Identitätsanbieter bleibt für andere verbundene Organisationen weiterhin bestehen.", "idpUnassociateDescription": "Alle Benutzer, die mit diesem Identitätsanbieter verbunden sind, werden aus dieser Organisation entfernt, aber der Identitätsanbieter bleibt für andere verbundene Organisationen weiterhin bestehen.",
"idpUnassociateConfirm": "Verknüpfung des Identitätsanbieters aufheben bestätigen", "idpUnassociateConfirm": "Verknüpfung des Identitätsanbieters aufheben bestätigen",
"idpConfirmDeleteAndRemoveMeFromOrg": "DELETE AND REMOVE ME FROM ORG",
"idpUnassociateAndRemoveMeFromOrg": "UNASSOCIATE AND REMOVE ME FROM ORG",
"idpUnassociateWarning": "Dies kann für diese Organisation nicht rückgängig gemacht werden.", "idpUnassociateWarning": "Dies kann für diese Organisation nicht rückgängig gemacht werden.",
"idpUnassociatedDescription": "Identitätsanbieter erfolgreich von dieser Organisation gelöst", "idpUnassociatedDescription": "Identitätsanbieter erfolgreich von dieser Organisation gelöst",
"idpUnassociateMenu": "Verknüpfung aufheben", "idpUnassociateMenu": "Verknüpfung aufheben",
@@ -3510,43 +3452,5 @@
"sshErrorNoTarget": "Kein Ziel angegeben", "sshErrorNoTarget": "Kein Ziel angegeben",
"sshErrorWebSocket": "WebSocket-Verbindung fehlgeschlagen", "sshErrorWebSocket": "WebSocket-Verbindung fehlgeschlagen",
"sshErrorAuthFailed": "Authentifizierung fehlgeschlagen", "sshErrorAuthFailed": "Authentifizierung fehlgeschlagen",
"sshErrorConnectionClosed": "Verbindung geschlossen, bevor die Authentifizierung abgeschlossen wurde", "sshErrorConnectionClosed": "Verbindung geschlossen, bevor die Authentifizierung abgeschlossen wurde"
"sitePangolinSshDescription": "Allow SSH access to resources on this site. This can be changed later.",
"browserGatewayNoResourceForDomain": "No resource found for this domain",
"browserGatewayNoTarget": "No target",
"browserGatewayConnect": "Connect",
"browserGatewayCtrlAltDel": "Ctrl+Alt+Del",
"sshErrorSignKeyFailed": "Failed to sign SSH key for PAM push authentication. Did you sign in as a user?",
"sshTerminalError": "Error: {error}",
"sshConnectionClosedCode": "Connection closed (code {code})",
"sshPrivateKeyPlaceholder": "-----BEGIN OPENSSH PRIVATE KEY-----",
"sshPrivateKeyRequired": "Private key is required",
"vncTitle": "VNC",
"vncSignInDescription": "Enter your VNC password to connect",
"vncPasswordOptional": "Password (optional)",
"vncNoResourceTarget": "No resource target is available",
"vncFailedToLoadNovnc": "Failed to load noVNC",
"vncAuthFailedStatus": "Status {status}",
"vncPasteClipboard": "Paste clipboard",
"rdpTitle": "RDP",
"rdpSignInTitle": "Sign in to Remote Desktop",
"rdpSignInDescription": "Enter Windows credentials to connect",
"rdpLoadingModule": "Loading module...",
"rdpFailedToLoadModule": "Failed to load RDP module",
"rdpNotReady": "Not ready",
"rdpModuleInitializing": "RDP module is still initializing",
"rdpDownloadingFiles": "Downloading {count} file(s) from remote…",
"rdpDownloadFailed": "Download failed: {fileName}",
"rdpUploaded": "Uploaded: {fileName}",
"rdpNoConnectionTarget": "No connection target available",
"rdpConnectionFailed": "Connection failed",
"rdpFit": "Fit",
"rdpFull": "Full",
"rdpReal": "Real",
"rdpMeta": "Meta",
"rdpUploadFiles": "Upload files",
"rdpFilesReadyToPaste": "Files ready to paste",
"rdpFilesReadyToPasteDescription": "{count} file(s) copied to remote clipboard — press Ctrl+V on the remote desktop to paste.",
"rdpUploadFailed": "Upload failed",
"rdpUnicodeKeyboardMode": "Unicode keyboard mode"
} }
+192 -50
View File
@@ -101,6 +101,8 @@
"sitesTableViewPrivateResources": "View Private Resources", "sitesTableViewPrivateResources": "View Private Resources",
"siteInstallNewt": "Install Site", "siteInstallNewt": "Install Site",
"siteInstallNewtDescription": "Install the site connector for your system", "siteInstallNewtDescription": "Install the site connector for your system",
"siteInstallKubernetesDocsDescription": "For more and up to date Kubernetes installation information, see <docsLink>docs.pangolin.net/manage/sites/install-kubernetes</docsLink>.",
"siteInstallAdvantechDocsDescription": "For Advantech modem installation instructions, see <docsLink>docs.pangolin.net/manage/sites/install-advantech</docsLink>.",
"WgConfiguration": "WireGuard Configuration", "WgConfiguration": "WireGuard Configuration",
"WgConfigurationDescription": "Use the following configuration to connect to the network", "WgConfigurationDescription": "Use the following configuration to connect to the network",
"operatingSystem": "Operating System", "operatingSystem": "Operating System",
@@ -148,16 +150,16 @@
"siteCredentialsSaveDescription": "You will only be able to see this once. Make sure to copy it to a secure place.", "siteCredentialsSaveDescription": "You will only be able to see this once. Make sure to copy it to a secure place.",
"siteInfo": "Site Information", "siteInfo": "Site Information",
"status": "Status", "status": "Status",
"shareTitle": "Manage Share Links", "shareTitle": "Manage Shareable Links",
"shareDescription": "Create shareable links to grant temporary or permanent access to proxy resources", "shareDescription": "Create shareable links to grant temporary or permanent access to proxy resources",
"shareSearch": "Search share links...", "shareSearch": "Search shareable links...",
"shareCreate": "Create Share Link", "shareCreate": "Create Shareable Link",
"shareErrorDelete": "Failed to delete link", "shareErrorDelete": "Failed to delete link",
"shareErrorDeleteMessage": "An error occurred deleting link", "shareErrorDeleteMessage": "An error occurred deleting link",
"shareDeleted": "Link deleted", "shareDeleted": "Link deleted",
"shareDeletedDescription": "The link has been deleted", "shareDeletedDescription": "The link has been deleted",
"shareDelete": "Delete Share Link", "shareDelete": "Delete Shareable Link",
"shareDeleteConfirm": "Confirm Delete Share Link", "shareDeleteConfirm": "Confirm Delete Shareable Link",
"shareQuestionRemove": "Are you sure you want to delete this share link?", "shareQuestionRemove": "Are you sure you want to delete this share link?",
"shareMessageRemove": "Once deleted, the link will no longer work and anyone using it will lose access to the resource.", "shareMessageRemove": "Once deleted, the link will no longer work and anyone using it will lose access to the resource.",
"shareTokenDescription": "The access token can be passed in two ways: as a query parameter or in the request headers. These must be passed from the client on every request for authenticated access.", "shareTokenDescription": "The access token can be passed in two ways: as a query parameter or in the request headers. These must be passed from the client on every request for authenticated access.",
@@ -177,6 +179,7 @@
"shareCreateDescription": "Anyone with this link can access the resource", "shareCreateDescription": "Anyone with this link can access the resource",
"shareTitleOptional": "Title (optional)", "shareTitleOptional": "Title (optional)",
"sharePathOptional": "Path (optional)", "sharePathOptional": "Path (optional)",
"sharePathDescription": "The link will redirect users to this path after authentication.",
"expireIn": "Expire In", "expireIn": "Expire In",
"neverExpire": "Never expire", "neverExpire": "Never expire",
"shareExpireDescription": "Expiration time is how long the link will be usable and provide access to the resource. After this time, the link will no longer work, and users who used this link will lose access to the resource.", "shareExpireDescription": "Expiration time is how long the link will be usable and provide access to the resource. After this time, the link will no longer work, and users who used this link will lose access to the resource.",
@@ -200,8 +203,8 @@
"shareErrorSelectResource": "Please select a resource", "shareErrorSelectResource": "Please select a resource",
"proxyResourceTitle": "Manage Public Resources", "proxyResourceTitle": "Manage Public Resources",
"proxyResourceDescription": "Create and manage resources that are publicly accessible through a web browser", "proxyResourceDescription": "Create and manage resources that are publicly accessible through a web browser",
"proxyResourcesBannerTitle": "Web-based Public Access", "publicResourcesBannerTitle": "Web-based Public Access",
"proxyResourcesBannerDescription": "Public resources are HTTPS proxies accessible to anyone on the internet through a web browser. Unlike private resources, they do not require client-side software and can include identity and context-aware access policies.", "publicResourcesBannerDescription": "Public resources are HTTPS proxies accessible to anyone on the internet through a web browser. Unlike private resources, they do not require client-side software and can include identity and context-aware access policies.",
"clientResourceTitle": "Manage Private Resources", "clientResourceTitle": "Manage Private Resources",
"clientResourceDescription": "Create and manage resources that are only accessible through a connected client", "clientResourceDescription": "Create and manage resources that are only accessible through a connected client",
"privateResourcesBannerTitle": "Zero-Trust Private Access", "privateResourcesBannerTitle": "Zero-Trust Private Access",
@@ -209,15 +212,19 @@
"resourcesSearch": "Search resources...", "resourcesSearch": "Search resources...",
"resourceAdd": "Add Resource", "resourceAdd": "Add Resource",
"resourceErrorDelte": "Error deleting resource", "resourceErrorDelte": "Error deleting resource",
"resourcePoliciesTitle": "Manage Resource Policies", "resourcePoliciesBannerTitle": "Re-use Authentication and Access Rules",
"resourcePoliciesAttachedResourcesColumnTitle": "Attached resources", "resourcePoliciesBannerDescription": "Shared resource policies let you define authentication methods and access rules once, then attach them to multiple public resources. When you update a policy, every linked resource inherits the change automatically.",
"resourcePoliciesBannerButtonText": "Learn More",
"resourcePoliciesTitle": "Manage Public Resource Policies",
"resourcePoliciesAttachedResourcesColumnTitle": "Resources",
"resourcePoliciesAttachedResources": "{count} resource(s)", "resourcePoliciesAttachedResources": "{count} resource(s)",
"resourcePoliciesAttachedResourcesCount": "{count, plural, one {# resource} other {# resources}}",
"resourcePoliciesAttachedResourcesEmpty": "no resources", "resourcePoliciesAttachedResourcesEmpty": "no resources",
"resourcePoliciesDescription": "Create and manage authentication policies to control access to your resources", "resourcePoliciesDescription": "Create and manage authentication policies to control access to your public resources",
"resourcePoliciesSearch": "Search policies...", "resourcePoliciesSearch": "Search policies...",
"resourcePoliciesAdd": "Add Policy", "resourcePoliciesAdd": "Add Policy",
"resourcePoliciesDefaultBadgeText": "Default policy", "resourcePoliciesDefaultBadgeText": "Default policy",
"resourcePoliciesCreate": "Create Resource Policy", "resourcePoliciesCreate": "Create Public Resource Policy",
"resourcePoliciesCreateDescription": "Follow the steps below to create a new policy", "resourcePoliciesCreateDescription": "Follow the steps below to create a new policy",
"resourcePolicyName": "Policy Name", "resourcePolicyName": "Policy Name",
"resourcePolicyNameDescription": "Give this policy a name to identify it across your resources", "resourcePolicyNameDescription": "Give this policy a name to identify it across your resources",
@@ -274,7 +281,7 @@
"back": "Back", "back": "Back",
"cancel": "Cancel", "cancel": "Cancel",
"resourceConfig": "Configuration Snippets", "resourceConfig": "Configuration Snippets",
"resourceConfigDescription": "Copy and paste these configuration snippets to set up the TCP/UDP resource", "resourceConfigDescription": "Copy and paste these configuration snippets to set up the TCP/UDP resource.",
"resourceAddEntrypoints": "Traefik: Add Entrypoints", "resourceAddEntrypoints": "Traefik: Add Entrypoints",
"resourceExposePorts": "Gerbil: Expose Ports in Docker Compose", "resourceExposePorts": "Gerbil: Expose Ports in Docker Compose",
"resourceLearnRaw": "Learn how to configure TCP/UDP resources", "resourceLearnRaw": "Learn how to configure TCP/UDP resources",
@@ -287,6 +294,8 @@
"labelDelete": "Delete Label", "labelDelete": "Delete Label",
"labelAdd": "Add Label", "labelAdd": "Add Label",
"labelCreateSuccessMessage": "Label Created Successfully", "labelCreateSuccessMessage": "Label Created Successfully",
"labelDuplicateError": "Duplicate Label",
"labelDuplicateErrorDescription": "A label with this name already exists.",
"labelEditSuccessMessage": "Label Modified Successfully", "labelEditSuccessMessage": "Label Modified Successfully",
"labelNameField": "Label Name", "labelNameField": "Label Name",
"labelColorField": "Label Color", "labelColorField": "Label Color",
@@ -311,7 +320,7 @@
"rules": "Rules", "rules": "Rules",
"resourceSettingDescription": "Configure the settings on the resource", "resourceSettingDescription": "Configure the settings on the resource",
"resourceSetting": "{resourceName} Settings", "resourceSetting": "{resourceName} Settings",
"resourcePolicySettingDescription": "Configure the settings on the resource policy", "resourcePolicySettingDescription": "Configure the settings on this public resource policy",
"resourcePolicySetting": "{policyName} Settings", "resourcePolicySetting": "{policyName} Settings",
"alwaysAllow": "Bypass Auth", "alwaysAllow": "Bypass Auth",
"alwaysDeny": "Block Access", "alwaysDeny": "Block Access",
@@ -719,7 +728,7 @@
"targetSubmit": "Add Target", "targetSubmit": "Add Target",
"targetNoOne": "This resource doesn't have any targets. Add a target to configure where to send requests to the backend.", "targetNoOne": "This resource doesn't have any targets. Add a target to configure where to send requests to the backend.",
"targetNoOneDescription": "Adding more than one target above will enable load balancing.", "targetNoOneDescription": "Adding more than one target above will enable load balancing.",
"targetsSubmit": "Save Targets", "targetsSubmit": "Save Settings",
"addTarget": "Add Target", "addTarget": "Add Target",
"proxyMultiSiteRoundRobinNodeHelp": "Round robin routing will not work between sites that are not connected to the same node, but failover will work.", "proxyMultiSiteRoundRobinNodeHelp": "Round robin routing will not work between sites that are not connected to the same node, but failover will work.",
"targetErrorInvalidIp": "Invalid IP address", "targetErrorInvalidIp": "Invalid IP address",
@@ -753,11 +762,11 @@
"rulesErrorDuplicate": "Duplicate rule", "rulesErrorDuplicate": "Duplicate rule",
"rulesErrorDuplicateDescription": "A rule with these settings already exists", "rulesErrorDuplicateDescription": "A rule with these settings already exists",
"rulesErrorInvalidIpAddressRange": "Invalid CIDR", "rulesErrorInvalidIpAddressRange": "Invalid CIDR",
"rulesErrorInvalidIpAddressRangeDescription": "Please enter a valid CIDR value", "rulesErrorInvalidIpAddressRangeDescription": "Enter a valid CIDR range (e.g., 10.0.0.0/8).",
"rulesErrorInvalidUrl": "Invalid URL path", "rulesErrorInvalidUrl": "Invalid path",
"rulesErrorInvalidUrlDescription": "Please enter a valid URL path value", "rulesErrorInvalidUrlDescription": "Enter a valid URL path or pattern (e.g., /api/*).",
"rulesErrorInvalidIpAddress": "Invalid IP", "rulesErrorInvalidIpAddress": "Invalid IP address",
"rulesErrorInvalidIpAddressDescription": "Please enter a valid IP address", "rulesErrorInvalidIpAddressDescription": "Enter a valid IPv4 or IPv6 address.",
"rulesErrorUpdate": "Failed to update rules", "rulesErrorUpdate": "Failed to update rules",
"rulesErrorUpdateDescription": "An error occurred while updating rules", "rulesErrorUpdateDescription": "An error occurred while updating rules",
"rulesUpdated": "Enable Rules", "rulesUpdated": "Enable Rules",
@@ -765,15 +774,24 @@
"rulesMatchIpAddressRangeDescription": "Enter an address in CIDR format (e.g., 103.21.244.0/22)", "rulesMatchIpAddressRangeDescription": "Enter an address in CIDR format (e.g., 103.21.244.0/22)",
"rulesMatchIpAddress": "Enter an IP address (e.g., 103.21.244.12)", "rulesMatchIpAddress": "Enter an IP address (e.g., 103.21.244.12)",
"rulesMatchUrl": "Enter a URL path or pattern (e.g., /api/v1/todos or /api/v1/*)", "rulesMatchUrl": "Enter a URL path or pattern (e.g., /api/v1/todos or /api/v1/*)",
"rulesErrorInvalidPriority": "Invalid Priority", "rulesErrorInvalidPriority": "Invalid priority",
"rulesErrorInvalidPriorityDescription": "Please enter a valid priority", "rulesErrorInvalidPriorityDescription": "Enter a whole number of 1 or higher.",
"rulesErrorDuplicatePriority": "Duplicate Priorities", "rulesErrorDuplicatePriority": "Duplicate priorities",
"rulesErrorDuplicatePriorityDescription": "Please enter unique priorities", "rulesErrorDuplicatePriorityDescription": "Each rule must have a unique priority number.",
"rulesErrorValidation": "Invalid rules",
"rulesErrorValidationRuleDescription": "Rule {ruleNumber}: {message}",
"rulesErrorInvalidMatchTypeDescription": "Select a valid match type (path, IP, CIDR, country, region, or ASN).",
"rulesErrorValueRequired": "Enter a value for this rule.",
"rulesErrorInvalidCountry": "Invalid country",
"rulesErrorInvalidCountryDescription": "Select a valid country.",
"rulesErrorInvalidAsn": "Invalid ASN",
"rulesErrorInvalidAsnDescription": "Enter a valid ASN (e.g., AS15169).",
"ruleUpdated": "Rules updated", "ruleUpdated": "Rules updated",
"ruleUpdatedDescription": "Rules updated successfully", "ruleUpdatedDescription": "Rules updated successfully",
"ruleErrorUpdate": "Operation failed", "ruleErrorUpdate": "Operation failed",
"ruleErrorUpdateDescription": "An error occurred during the save operation", "ruleErrorUpdateDescription": "An error occurred during the save operation",
"rulesPriority": "Priority", "rulesPriority": "Priority",
"rulesReorderDragHandle": "Drag to reorder rule priority",
"rulesAction": "Action", "rulesAction": "Action",
"rulesMatchType": "Match Type", "rulesMatchType": "Match Type",
"value": "Value", "value": "Value",
@@ -792,7 +810,7 @@
"rulesResource": "Resource Rules Configuration", "rulesResource": "Resource Rules Configuration",
"rulesResourceDescription": "Configure rules to control access to the resource", "rulesResourceDescription": "Configure rules to control access to the resource",
"ruleSubmit": "Add Rule", "ruleSubmit": "Add Rule",
"rulesNoOne": "No rules. Add a rule using the form.", "rulesNoOne": "No rules yet.",
"rulesOrder": "Rules are evaluated by priority in ascending order.", "rulesOrder": "Rules are evaluated by priority in ascending order.",
"rulesSubmit": "Save Rules", "rulesSubmit": "Save Rules",
"policyErrorCreate": "Error creating policy", "policyErrorCreate": "Error creating policy",
@@ -803,7 +821,48 @@
"policyErrorUpdateMessageDescription": "An unexpected error occurred", "policyErrorUpdateMessageDescription": "An unexpected error occurred",
"policyCreatedSuccess": "Resource policy succesfully created", "policyCreatedSuccess": "Resource policy succesfully created",
"policyUpdatedSuccess": "Resource policy succesfully updated", "policyUpdatedSuccess": "Resource policy succesfully updated",
"authMethodsSave": "Save auth methods", "authMethodsSave": "Save Settings",
"policyAuthStackTitle": "Authentication",
"policyAuthStackDescription": "Control which authentication methods are required to access this resource",
"policyAuthOrLogicTitle": "Multiple authentication methods active",
"policyAuthOrLogicBanner": "Visitors may authenticate using any one of the active methods below. They do not need to complete all of them.",
"policyAuthMethodActive": "Active",
"policyAuthMethodOff": "Off",
"policyAuthSsoTitle": "Platform SSO",
"policyAuthSsoDescription": "Require sign-in through your organization's identity provider",
"policyAuthSsoSummary": "{idp} · {users} users, {roles} roles",
"policyAuthSsoDefaultIdp": "Default provider",
"policyAuthAddDefaultIdentityProvider": "Add Default Identity Provider",
"policyAuthOtherMethodsTitle": "Other Methods",
"policyAuthOtherMethodsDescription": "Optional methods visitors can use instead of or alongside platform SSO",
"policyAuthPasscodeTitle": "Passcode",
"policyAuthPasscodeDescription": "Require a shared alphanumeric passcode to access the resource",
"policyAuthPasscodeSummary": "Passcode set",
"policyAuthPincodeTitle": "PIN Code",
"policyAuthPincodeDescription": "A short numeric code required to access the resource",
"policyAuthPincodeSummary": "6-digit PIN set",
"policyAuthEmailTitle": "Email Whitelist",
"policyAuthEmailDescription": "Allow listed email addresses with one-time passwords",
"policyAuthEmailSummary": "{count} addresses allowed",
"policyAuthEmailOtpCallout": "Enabling email whitelist sends a one-time password to the visitor's email on login.",
"policyAuthHeaderAuthTitle": "Basic Header Auth",
"policyAuthHeaderAuthDescription": "Validate a custom HTTP header name and value on each request",
"policyAuthHeaderAuthSummary": "Header configured",
"policyAuthHeaderName": "Header name",
"policyAuthHeaderValue": "Expected value",
"policyAuthSetPasscode": "Set Passcode",
"policyAuthSetPincode": "Set PIN Code",
"policyAuthSetEmailWhitelist": "Set Email Whitelist",
"policyAuthSetHeaderAuth": "Set Basic Header Auth",
"policyAccessRulesTitle": "Access Rules",
"policyAccessRulesEnableDescription": "When enabled, rules are evaluated in descending order until one evaluates as true.",
"policyAccessRulesFirstMatch": "Rules are evaluated top to bottom. The first matching rule decides the outcome.",
"policyAccessRulesHowItWorks": "Rules match requests by path, IP address, location, or other criteria. Each rule applies an action: bypass authentication, block access, or pass to authentication. If no rule matches, traffic continues to authentication.",
"policyAccessRulesFallthroughOff": "When rules are disabled, all traffic passes through to authentication.",
"policyAccessRulesFallthroughOn": "When no rule matches, traffic passes through to authentication.",
"rulesPlaceholderCidr": "10.0.0.0/8",
"rulesPlaceholderPath": "/admin/*",
"rulesPlaceholderGeo": "RU, KP",
"rulesSave": "Save Rules", "rulesSave": "Save Rules",
"resourceErrorCreate": "Error creating resource", "resourceErrorCreate": "Error creating resource",
"resourceErrorCreateDescription": "An error occurred when creating the resource", "resourceErrorCreateDescription": "An error occurred when creating the resource",
@@ -824,9 +883,9 @@
"resourcesErrorUpdateDescription": "An error occurred while updating the resource", "resourcesErrorUpdateDescription": "An error occurred while updating the resource",
"access": "Access", "access": "Access",
"accessControl": "Access Control", "accessControl": "Access Control",
"shareLink": "{resource} Share Link", "shareLink": "{resource} Shareable Link",
"resourceSelect": "Select resource", "resourceSelect": "Select resource",
"shareLinks": "Share Links", "shareLinks": "Shareable Links",
"share": "Shareable Links", "share": "Shareable Links",
"shareDescription2": "Create shareable links to resources. Links provide temporary or unlimited access to your resource. You can configure the expiration duration of the link when you create one.", "shareDescription2": "Create shareable links to resources. Links provide temporary or unlimited access to your resource. You can configure the expiration duration of the link when you create one.",
"shareEasyCreate": "Easy to create and share", "shareEasyCreate": "Easy to create and share",
@@ -916,10 +975,18 @@
"resourceRoleDescription": "Admins can always access this resource.", "resourceRoleDescription": "Admins can always access this resource.",
"resourcePolicySelectTitle": "Resource Access Policy", "resourcePolicySelectTitle": "Resource Access Policy",
"resourcePolicySelectDescription": "Select the resource policy type for authentication", "resourcePolicySelectDescription": "Select the resource policy type for authentication",
"resourcePolicyTypeLabel": "Policy type",
"resourcePolicyLabel": "Resource policy",
"resourcePolicyInline": "Inline Resource Policy", "resourcePolicyInline": "Inline Resource Policy",
"resourcePolicyInlineDescription": "Access Policy scoped to only this resource", "resourcePolicyInlineDescription": "Access Policy scoped to only this resource",
"resourcePolicyShared": "Shared Resource Policy", "resourcePolicyShared": "Shared Resource Policy",
"resourcePolicySharedDescription": "This resource uses a shared policy. Policy-level settings (auth methods, email whitelist) are locked. You can add resource-specific rules, roles, and users below.", "resourcePolicySharedDescription": "This resource uses a shared policy.",
"sharedPolicy": "Shared Policy",
"sharedPolicyNoneDescription": "This resource has its own policy.",
"resourceSharedPolicyOwnDescription": "This resource has its own authentication and access rules controls.",
"resourceSharedPolicyInheritedDescription": "This resource inherits from <policyLink>{policyName}</policyLink>.",
"resourceSharedPolicyAuthenticationNotice": "This resource is using a shared policy. Some authentication settings can be edited on this resource to add to the policy. To change the underlying policy, you must edit to <policyLink>{policyName}</policyLink>.",
"resourceSharedPolicyRulesNotice": "This resource is using a shared policy. Some access rules can be edited on this resource. To change the underlying policy, you must edit <policyLink>{policyName}</policyLink>.",
"resourceUsersRoles": "Access Controls", "resourceUsersRoles": "Access Controls",
"resourceUsersRolesDescription": "Configure which users and roles can visit this resource", "resourceUsersRolesDescription": "Configure which users and roles can visit this resource",
"resourceUsersRolesSubmit": "Save Access Controls", "resourceUsersRolesSubmit": "Save Access Controls",
@@ -944,7 +1011,14 @@
"resourceVisibilityTitle": "Visibility", "resourceVisibilityTitle": "Visibility",
"resourceVisibilityTitleDescription": "Completely enable or disable resource visibility", "resourceVisibilityTitleDescription": "Completely enable or disable resource visibility",
"resourceGeneral": "General Settings", "resourceGeneral": "General Settings",
"resourceGeneralDescription": "Configure the general settings for this resource", "resourceGeneralDescription": "Configure name, address, and access policy for this resource.",
"resourceGeneralDetailsSubsection": "Resource Details",
"resourceGeneralDetailsSubsectionDescription": "Set the display name, identifier, and publicly accessible domain for this resource.",
"resourceGeneralDetailsSubsectionPortDescription": "Set the display name, identifier, and public port for this resource.",
"resourceGeneralPublicAddressSubsection": "Public Address",
"resourceGeneralPublicAddressSubsectionDescription": "Configure how users reach this resource.",
"resourceGeneralAuthenticationAccessSubsection": "Authentication & Access",
"resourceGeneralAuthenticationAccessSubsectionDescription": "Choose whether this resource uses its own policy or inherits from a shared policy.",
"resourceEnable": "Enable Resource", "resourceEnable": "Enable Resource",
"resourceTransfer": "Transfer Resource", "resourceTransfer": "Transfer Resource",
"resourceTransferDescription": "Transfer this resource to a different site", "resourceTransferDescription": "Transfer this resource to a different site",
@@ -1220,11 +1294,14 @@
"addLabels": "Add labels", "addLabels": "Add labels",
"siteLabelsTab": "Labels", "siteLabelsTab": "Labels",
"siteLabelsDescription": "Manage labels associated with this site.", "siteLabelsDescription": "Manage labels associated with this site.",
"labelsNotFound": "Labels not found", "labelsNotFound": "No labels found.",
"labelsEmptyCreateHint": "Start typing above to create a label.",
"labelSearch": "Search labels", "labelSearch": "Search labels",
"labelSearchOrCreate": "Search or create a label",
"accessLabelFilterCount": "{count, plural, one {# label} other {# labels}}", "accessLabelFilterCount": "{count, plural, one {# label} other {# labels}}",
"labelOverflowCount": "+{count, plural, one {# label} other {# labels}}", "labelOverflowCount": "+{count, plural, one {# label} other {# labels}}",
"accessLabelFilterClear": "Clear label filters", "accessLabelFilterClear": "Clear label filters",
"accessFilterClear": "Clear filters",
"selectColor": "Select color", "selectColor": "Select color",
"createNewLabel": "Create new org label \"{label}\"", "createNewLabel": "Create new org label \"{label}\"",
"inviteInvalidDescription": "The invite link is invalid.", "inviteInvalidDescription": "The invite link is invalid.",
@@ -1461,8 +1538,8 @@
"sidebarResources": "Resources", "sidebarResources": "Resources",
"sidebarProxyResources": "Public", "sidebarProxyResources": "Public",
"sidebarClientResources": "Private", "sidebarClientResources": "Private",
"sidebarPolicies": "Policies", "sidebarPolicies": "Shared Policies",
"sidebarResourcePolicies": "Resources", "sidebarResourcePolicies": "Public Resources",
"sidebarAccessControl": "Access Control", "sidebarAccessControl": "Access Control",
"sidebarLogsAndAnalytics": "Logs & Analytics", "sidebarLogsAndAnalytics": "Logs & Analytics",
"sidebarTeam": "Team", "sidebarTeam": "Team",
@@ -1470,7 +1547,7 @@
"sidebarAdmin": "Admin", "sidebarAdmin": "Admin",
"sidebarInvitations": "Invitations", "sidebarInvitations": "Invitations",
"sidebarRoles": "Roles", "sidebarRoles": "Roles",
"sidebarShareableLinks": "Links", "sidebarShareableLinks": "Shareable Links",
"sidebarApiKeys": "API Keys", "sidebarApiKeys": "API Keys",
"sidebarProvisioning": "Provisioning", "sidebarProvisioning": "Provisioning",
"sidebarSettings": "Settings", "sidebarSettings": "Settings",
@@ -1647,7 +1724,7 @@
"standaloneHcFilterResourceIdFallback": "Resource {id}", "standaloneHcFilterResourceIdFallback": "Resource {id}",
"blueprints": "Blueprints", "blueprints": "Blueprints",
"blueprintsLog": "Blueprints Log", "blueprintsLog": "Blueprints Log",
"blueprintsDescription": "View past blueprint applications and their results", "blueprintsDescription": "View past blueprint applications and their results or apply a new blueprint",
"blueprintAdd": "Add Blueprint", "blueprintAdd": "Add Blueprint",
"blueprintGoBack": "See all Blueprints", "blueprintGoBack": "See all Blueprints",
"blueprintCreate": "Create Blueprint", "blueprintCreate": "Create Blueprint",
@@ -1667,10 +1744,10 @@
"enableDockerSocket": "Enable Docker Blueprint", "enableDockerSocket": "Enable Docker Blueprint",
"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>.", "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", "newtAutoUpdate": "Enable Site Auto-Update",
"newtAutoUpdateDescription": "When enabled, site connectors will automatically update to the latest version when a new release is available.", "newtAutoUpdateDescription": "When enabled, site connectors will automatically download the latest version and restart themselves. This can be overridden on a per-site basis.",
"siteAutoUpdate": "Site Auto-Update", "siteAutoUpdate": "Site Auto-Update",
"siteAutoUpdateLabel": "Enable Auto-Update", "siteAutoUpdateLabel": "Enable Auto-Update",
"siteAutoUpdateDescription": "Control whether this site's connector automatically downloads the latest version.", "siteAutoUpdateDescription": "When enabled, this site's connector will automatically download the latest version and restart itself.",
"siteAutoUpdateOrgDefault": "Organization default: {state}", "siteAutoUpdateOrgDefault": "Organization default: {state}",
"siteAutoUpdateOverriding": "Overriding organization setting", "siteAutoUpdateOverriding": "Overriding organization setting",
"siteAutoUpdateResetToOrg": "Reset to Organization Default", "siteAutoUpdateResetToOrg": "Reset to Organization Default",
@@ -1768,9 +1845,9 @@
"accountSetupSuccess": "Account setup completed! Welcome to Pangolin!", "accountSetupSuccess": "Account setup completed! Welcome to Pangolin!",
"documentation": "Documentation", "documentation": "Documentation",
"saveAllSettings": "Save All Settings", "saveAllSettings": "Save All Settings",
"saveResourceTargets": "Save Targets", "saveResourceTargets": "Save Settings",
"saveResourceHttp": "Save Proxy Settings", "saveResourceHttp": "Save Settings",
"saveProxyProtocol": "Save Proxy protocol settings", "saveProxyProtocol": "Save Settings",
"settingsUpdated": "Settings updated", "settingsUpdated": "Settings updated",
"settingsUpdatedDescription": "Settings updated successfully", "settingsUpdatedDescription": "Settings updated successfully",
"settingsErrorUpdate": "Failed to update settings", "settingsErrorUpdate": "Failed to update settings",
@@ -2027,13 +2104,13 @@
"healthCheckUnknown": "Unknown", "healthCheckUnknown": "Unknown",
"healthCheck": "Health Check", "healthCheck": "Health Check",
"configureHealthCheck": "Configure Health Check", "configureHealthCheck": "Configure Health Check",
"configureHealthCheckDescription": "Set up health monitoring for {target}", "configureHealthCheckDescription": "Set up monitoring for your resource to ensure it is always available",
"enableHealthChecks": "Enable Health Checks", "enableHealthChecks": "Enable Health Checks",
"healthCheckDisabledStateDescription": "When disabled, the site will not perform health checks and the state will be considered unknown.", "healthCheckDisabledStateDescription": "When disabled, the site will not perform health checks and the state will be considered unknown.",
"enableHealthChecksDescription": "Monitor the health of this target. You can monitor a different endpoint than the target if required.", "enableHealthChecksDescription": "Monitor the health of this target. You can monitor a different endpoint than the target if required.",
"healthScheme": "Method", "healthScheme": "Method",
"healthSelectScheme": "Select Method", "healthSelectScheme": "Select Method",
"healthCheckPortInvalid": "Health check port must be between 1 and 65535", "healthCheckPortInvalid": "Port must be between 1 and 65535",
"healthCheckPath": "Path", "healthCheckPath": "Path",
"healthHostname": "IP / Host", "healthHostname": "IP / Host",
"healthPort": "Port", "healthPort": "Port",
@@ -2046,6 +2123,7 @@
"requireDeviceApproval": "Require Device Approvals", "requireDeviceApproval": "Require Device Approvals",
"requireDeviceApprovalDescription": "Users with this role need new devices approved by an admin before they can connect and access resources.", "requireDeviceApprovalDescription": "Users with this role need new devices approved by an admin before they can connect and access resources.",
"sshSettings": "SSH Settings", "sshSettings": "SSH Settings",
"sshAccess": "SSH Access",
"rdpSettings": "RDP Settings", "rdpSettings": "RDP Settings",
"vncSettings": "VNC Settings", "vncSettings": "VNC Settings",
"sshServer": "SSH Server", "sshServer": "SSH Server",
@@ -2072,8 +2150,13 @@
"sshDaemonDisclaimer": "Ensure your target host is properly configured to run the auth daemon before completing this setup, or provisioning will fail.", "sshDaemonDisclaimer": "Ensure your target host is properly configured to run the auth daemon before completing this setup, or provisioning will fail.",
"sshDaemonPort": "Daemon Port", "sshDaemonPort": "Daemon Port",
"sshServerDestination": "Server Destination", "sshServerDestination": "Server Destination",
"sshServerDestinationDescription": "Configure the destination and port of the SSH server", "sshServerDestinationDescription": "Configure the destination of the SSH server",
"destination": "Destination", "destination": "Destination",
"destinationRequired": "Destination is required.",
"domainRequired": "Domain is required.",
"proxyPortRequired": "Port is required.",
"invalidPathConfiguration": "Invalid path configuration.",
"invalidRewritePathConfiguration": "Invalid rewrite path configuration.",
"bgTargetMultiSiteDisclaimer": "Selecting multiple sites enables resilient routing and failover for high availability.", "bgTargetMultiSiteDisclaimer": "Selecting multiple sites enables resilient routing and failover for high availability.",
"roleAllowSsh": "Allow SSH", "roleAllowSsh": "Allow SSH",
"roleAllowSshAllow": "Allow", "roleAllowSshAllow": "Allow",
@@ -2088,10 +2171,25 @@
"sshSudoModeCommandsDescription": "User can run only the specified commands with sudo.", "sshSudoModeCommandsDescription": "User can run only the specified commands with sudo.",
"sshSudo": "Allow sudo", "sshSudo": "Allow sudo",
"sshSudoCommands": "Sudo Commands", "sshSudoCommands": "Sudo Commands",
"sshSudoCommandsDescription": "Comma separated list of commands the user is allowed to run with sudo. Absolute paths must be used.", "sshSudoCommandsDescription": "List of commands the user is allowed to run with sudo, separated by commas, spaces, or new lines. Absolute paths must be used.",
"sshCreateHomeDir": "Create Home Directory", "sshCreateHomeDir": "Create Home Directory",
"sshUnixGroups": "Unix Groups", "sshUnixGroups": "Unix Groups",
"sshUnixGroupsDescription": "Comma separated Unix groups to add the user to on the target host.", "sshUnixGroupsDescription": "Unix groups to add the user to on the target host, separated by commas, spaces, or new lines.",
"roleTextFieldPlaceholder": "Enter values, or drop a .txt or .csv file",
"roleTextImportTitle": "Import from File",
"roleTextImportDescription": "Importing {fileName} into {fieldLabel}.",
"roleTextImportSkipHeader": "Skip First Row (Header)",
"roleTextImportOverride": "Replace Existing",
"roleTextImportAppend": "Append to Existing",
"roleTextImportMode": "Import Mode",
"roleTextImportPreview": "Preview",
"roleTextImportItemCount": "{count, plural, =0 {No items to import} one {1 item to import} other {# items to import}}",
"roleTextImportTotalCount": "{existing} existing + {imported} imported = {total} total",
"roleTextImportConfirm": "Import",
"roleTextImportInvalidFile": "Unsupported file type",
"roleTextImportInvalidFileDescription": "Only .txt and .csv files are supported.",
"roleTextImportEmpty": "No items found in file",
"roleTextImportEmptyDescription": "The file does not contain any importable items.",
"retryAttempts": "Retry Attempts", "retryAttempts": "Retry Attempts",
"expectedResponseCodes": "Expected Response Codes", "expectedResponseCodes": "Expected Response Codes",
"expectedResponseCodesDescription": "HTTP status code that indicates healthy status. If left blank, 200-300 is considered healthy.", "expectedResponseCodesDescription": "HTTP status code that indicates healthy status. If left blank, 200-300 is considered healthy.",
@@ -2875,9 +2973,10 @@
"enableProxyProtocol": "Enable Proxy Protocol", "enableProxyProtocol": "Enable Proxy Protocol",
"proxyProtocolInfo": "Preserve client IP addresses for TCP backends", "proxyProtocolInfo": "Preserve client IP addresses for TCP backends",
"proxyProtocolVersion": "Proxy Protocol Version", "proxyProtocolVersion": "Proxy Protocol Version",
"version1": " Version 1 (Recommended)", "version1": "Version 1 (Recommended)",
"version2": "Version 2", "version2": "Version 2",
"versionDescription": "Version 1 is text-based and widely supported. Version 2 is binary and more efficient but less compatible. Make sure servers transport is added to dynamic config.", "version1Description": "Text-based and widely supported. Make sure servers transport is added to dynamic config.",
"version2Description": "Binary and more efficient but less compatible. Make sure servers transport is added to dynamic config.",
"warning": "Warning", "warning": "Warning",
"proxyProtocolWarning": "The backend application must be configured to accept Proxy Protocol connections. If your backend doesn't support Proxy Protocol, enabling this will break all connections so only enable this if you know what you're doing. Make sure to configure your backend to trust Proxy Protocol headers from Traefik.", "proxyProtocolWarning": "The backend application must be configured to accept Proxy Protocol connections. If your backend doesn't support Proxy Protocol, enabling this will break all connections so only enable this if you know what you're doing. Make sure to configure your backend to trust Proxy Protocol headers from Traefik.",
"restarting": "Restarting...", "restarting": "Restarting...",
@@ -3034,7 +3133,7 @@
"enterConfirmation": "Enter confirmation", "enterConfirmation": "Enter confirmation",
"blueprintViewDetails": "Details", "blueprintViewDetails": "Details",
"defaultIdentityProvider": "Default Identity Provider", "defaultIdentityProvider": "Default Identity Provider",
"defaultIdentityProviderDescription": "When a default identity provider is selected, the user will be automatically redirected to the provider for authentication.", "defaultIdentityProviderDescription": "The user will be automatically redirected to this identity provider for authentication.",
"editInternalResourceDialogNetworkSettings": "Network Settings", "editInternalResourceDialogNetworkSettings": "Network Settings",
"editInternalResourceDialogAccessPolicy": "Access Policy", "editInternalResourceDialogAccessPolicy": "Access Policy",
"editInternalResourceDialogAddRoles": "Add Roles", "editInternalResourceDialogAddRoles": "Add Roles",
@@ -3075,6 +3174,7 @@
"maintenanceModeType": "Maintenance Mode Type", "maintenanceModeType": "Maintenance Mode Type",
"showMaintenancePage": "Show a maintenance page to visitors", "showMaintenancePage": "Show a maintenance page to visitors",
"enableMaintenanceMode": "Enable Maintenance Mode", "enableMaintenanceMode": "Enable Maintenance Mode",
"enableMaintenanceModeDescription": "When enabled, visitors will see a maintenance page instead of your resource.",
"automatic": "Automatic", "automatic": "Automatic",
"automaticModeDescription": " Show maintenance page only when all backend targets are down or unhealthy. Your resource continues working normally as long as at least one target is healthy.", "automaticModeDescription": " Show maintenance page only when all backend targets are down or unhealthy. Your resource continues working normally as long as at least one target is healthy.",
"forced": "Forced", "forced": "Forced",
@@ -3082,6 +3182,8 @@
"warning:": "Warning:", "warning:": "Warning:",
"forcedeModeWarning": "All traffic will be directed to the maintenance page. Your backend resources will not receive any requests.", "forcedeModeWarning": "All traffic will be directed to the maintenance page. Your backend resources will not receive any requests.",
"pageTitle": "Page Title", "pageTitle": "Page Title",
"maintenancePageContentSubsection": "Page Content",
"maintenancePageContentSubsectionDescription": "Customize the content displayed on the maintenance page",
"pageTitleDescription": "The main heading displayed on the maintenance page", "pageTitleDescription": "The main heading displayed on the maintenance page",
"maintenancePageMessage": "Maintenance Message", "maintenancePageMessage": "Maintenance Message",
"maintenancePageMessagePlaceholder": "We'll be back soon! Our site is currently undergoing scheduled maintenance.", "maintenancePageMessagePlaceholder": "We'll be back soon! Our site is currently undergoing scheduled maintenance.",
@@ -3441,18 +3543,58 @@
"sshConnecting": "Connecting…", "sshConnecting": "Connecting…",
"sshInitializing": "Initializing…", "sshInitializing": "Initializing…",
"sshSignInTitle": "Sign in to SSH", "sshSignInTitle": "Sign in to SSH",
"sshSignInDescription": "Enter your SSH credentials", "sshSignInDescription": "Enter your SSH credentials to connect",
"sshPasswordTab": "Password", "sshPasswordTab": "Password",
"sshPrivateKeyTab": "Private Key", "sshPrivateKeyTab": "Private Key",
"sshPrivateKeyField": "Private Key", "sshPrivateKeyField": "Private Key",
"sshPrivateKeyDisclaimer": "Your private key is not stored or visible to Pangolin. Alternatively, you can use short-lived certificates for seamless authentication using your existing Pangolin identity.", "sshPrivateKeyDisclaimer": "Your private key is not stored or visible to Pangolin. Alternatively, you can use short-lived certificates for seamless authentication using your existing Pangolin identity.",
"sshLearnMore": "Learn more", "sshLearnMore": "Learn more",
"sshPrivateKeyFile": "Private Key File", "sshPrivateKeyFile": "Private Key File",
"sshAuthenticate": "Authenticate", "sshAuthenticate": "Connect",
"sshTerminate": "Terminate", "sshTerminate": "Terminate",
"sshPoweredBy": "Powered by", "sshPoweredBy": "Powered by",
"sshErrorNoTarget": "No target specified", "sshErrorNoTarget": "No target specified",
"sshErrorWebSocket": "WebSocket connection failed", "sshErrorWebSocket": "WebSocket connection failed",
"sshErrorAuthFailed": "Authentication failed", "sshErrorAuthFailed": "Authentication failed",
"sshErrorConnectionClosed": "Connection closed before authentication completed" "sshErrorConnectionClosed": "Connection closed before authentication completed",
"sitePangolinSshDescription": "Allow SSH access to resources on this site. This can be changed later.",
"browserGatewayNoResourceForDomain": "No resource found for this domain",
"browserGatewayNoTarget": "No target",
"browserGatewayConnect": "Connect",
"browserGatewayCtrlAltDel": "Ctrl+Alt+Del",
"sshErrorSignKeyFailed": "Failed to sign SSH key for PAM push authentication. Did you sign in as a user?",
"sshTerminalError": "Error: {error}",
"sshConnectionClosedCode": "Connection closed (code {code})",
"sshPrivateKeyPlaceholder": "-----BEGIN OPENSSH PRIVATE KEY-----",
"sshPrivateKeyRequired": "Private key is required",
"vncTitle": "VNC",
"vncSignInDescription": "Enter your VNC password to connect",
"vncPasswordOptional": "Password (optional)",
"vncNoResourceTarget": "No resource target is available",
"vncFailedToLoadNovnc": "Failed to load noVNC",
"vncAuthFailedStatus": "Status {status}",
"vncPasteClipboard": "Paste clipboard",
"rdpTitle": "RDP",
"rdpSignInTitle": "Sign in to Remote Desktop",
"rdpSignInDescription": "Enter Windows credentials to connect",
"rdpLoadingModule": "Loading module...",
"rdpFailedToLoadModule": "Failed to load RDP module",
"rdpNotReady": "Not ready",
"rdpModuleInitializing": "RDP module is still initializing",
"rdpDownloadingFiles": "Downloading {count} file(s) from remote…",
"rdpDownloadFailed": "Download failed: {fileName}",
"rdpUploaded": "Uploaded: {fileName}",
"rdpNoConnectionTarget": "No connection target available",
"rdpConnectionFailed": "Connection failed",
"rdpFit": "Fit",
"rdpFull": "Full",
"rdpReal": "Real",
"rdpMeta": "Meta",
"rdpUploadFiles": "Upload files",
"rdpFilesReadyToPaste": "Files ready to paste",
"rdpFilesReadyToPasteDescription": "{count} file(s) copied to remote clipboard — press Ctrl+V on the remote desktop to paste.",
"rdpUploadFailed": "Upload failed",
"rdpUnicodeKeyboardMode": "Unicode keyboard mode",
"sessionToolbarShow": "Show toolbar",
"sessionToolbarHide": "Hide toolbar"
} }
+27 -123
View File
@@ -101,8 +101,6 @@
"sitesTableViewPrivateResources": "Ver Recursos Privados", "sitesTableViewPrivateResources": "Ver Recursos Privados",
"siteInstallNewt": "Instalar Newt", "siteInstallNewt": "Instalar Newt",
"siteInstallNewtDescription": "Recibe Newt corriendo en tu sistema", "siteInstallNewtDescription": "Recibe Newt corriendo en tu sistema",
"siteInstallKubernetesDocsDescription": "For more and up to date Kubernetes installation information, see <docsLink>docs.pangolin.net/manage/sites/install-kubernetes</docsLink>.",
"siteInstallAdvantechDocsDescription": "For Advantech modem installation instructions, see <docsLink>docs.pangolin.net/manage/sites/install-advantech</docsLink>.",
"WgConfiguration": "Configuración de Wirex Guard", "WgConfiguration": "Configuración de Wirex Guard",
"WgConfigurationDescription": "Utilice la siguiente configuración para conectarse a la red", "WgConfigurationDescription": "Utilice la siguiente configuración para conectarse a la red",
"operatingSystem": "Sistema operativo", "operatingSystem": "Sistema operativo",
@@ -202,8 +200,8 @@
"shareErrorSelectResource": "Por favor, seleccione un recurso", "shareErrorSelectResource": "Por favor, seleccione un recurso",
"proxyResourceTitle": "Administrar recursos públicos", "proxyResourceTitle": "Administrar recursos públicos",
"proxyResourceDescription": "Crear y administrar recursos que sean accesibles públicamente a través de un navegador web", "proxyResourceDescription": "Crear y administrar recursos que sean accesibles públicamente a través de un navegador web",
"publicResourcesBannerTitle": "Web-based Public Access", "publicResourcesBannerTitle": "Acceso público basado en web",
"publicResourcesBannerDescription": "Public resources are HTTPS proxies accessible to anyone on the internet through a web browser. Unlike private resources, they do not require client-side software and can include identity and context-aware access policies.", "publicResourcesBannerDescription": "Los recursos públicos son proxies HTTPS o TCP/UDP accesibles a cualquiera en Internet a través de un navegador web. A diferencia de los recursos privados, no requieren software del lado del cliente e incluye políticas de acceso basadas en identidad y contexto.",
"clientResourceTitle": "Administrar recursos privados", "clientResourceTitle": "Administrar recursos privados",
"clientResourceDescription": "Crear y administrar recursos que sólo son accesibles a través de un cliente conectado", "clientResourceDescription": "Crear y administrar recursos que sólo son accesibles a través de un cliente conectado",
"privateResourcesBannerTitle": "Acceso privado de confianza cero", "privateResourcesBannerTitle": "Acceso privado de confianza cero",
@@ -211,16 +209,15 @@
"resourcesSearch": "Buscar recursos...", "resourcesSearch": "Buscar recursos...",
"resourceAdd": "Añadir Recurso", "resourceAdd": "Añadir Recurso",
"resourceErrorDelte": "Error al eliminar el recurso", "resourceErrorDelte": "Error al eliminar el recurso",
"resourcePoliciesTitle": "Manage Public Resource Policies", "resourcePoliciesTitle": "Administrar Políticas de Recursos",
"resourcePoliciesAttachedResourcesColumnTitle": "Resources", "resourcePoliciesAttachedResourcesColumnTitle": "Recursos Adjuntos",
"resourcePoliciesAttachedResources": "{count} recurso/s", "resourcePoliciesAttachedResources": "{count} recurso/s",
"resourcePoliciesAttachedResourcesCount": "{count, plural, one {# resource} other {# resources}}",
"resourcePoliciesAttachedResourcesEmpty": "sin recursos", "resourcePoliciesAttachedResourcesEmpty": "sin recursos",
"resourcePoliciesDescription": "Create and manage authentication policies to control access to your public resources", "resourcePoliciesDescription": "Cree y administre políticas de autenticación para controlar el acceso a sus recursos",
"resourcePoliciesSearch": "Buscar políticas...", "resourcePoliciesSearch": "Buscar políticas...",
"resourcePoliciesAdd": "Agregar Política", "resourcePoliciesAdd": "Agregar Política",
"resourcePoliciesDefaultBadgeText": "Política predeterminada", "resourcePoliciesDefaultBadgeText": "Política predeterminada",
"resourcePoliciesCreate": "Create Public Resource Policy", "resourcePoliciesCreate": "Crear Política de Recursos",
"resourcePoliciesCreateDescription": "Siga los pasos a continuación para crear una nueva política", "resourcePoliciesCreateDescription": "Siga los pasos a continuación para crear una nueva política",
"resourcePolicyName": "Nombre de la política", "resourcePolicyName": "Nombre de la política",
"resourcePolicyNameDescription": "Déle a esta política un nombre para identificarla en sus recursos", "resourcePolicyNameDescription": "Déle a esta política un nombre para identificarla en sus recursos",
@@ -314,7 +311,7 @@
"rules": "Reglas", "rules": "Reglas",
"resourceSettingDescription": "Configurar la configuración del recurso", "resourceSettingDescription": "Configurar la configuración del recurso",
"resourceSetting": "Ajustes {resourceName}", "resourceSetting": "Ajustes {resourceName}",
"resourcePolicySettingDescription": "Configure the settings on this public resource policy", "resourcePolicySettingDescription": "Configure la configuración en la política de recursos",
"resourcePolicySetting": "Configuración {policyName}", "resourcePolicySetting": "Configuración {policyName}",
"alwaysAllow": "Autorización Bypass", "alwaysAllow": "Autorización Bypass",
"alwaysDeny": "Bloquear acceso", "alwaysDeny": "Bloquear acceso",
@@ -756,11 +753,11 @@
"rulesErrorDuplicate": "Duplicar regla", "rulesErrorDuplicate": "Duplicar regla",
"rulesErrorDuplicateDescription": "Ya existe una regla con estos ajustes", "rulesErrorDuplicateDescription": "Ya existe una regla con estos ajustes",
"rulesErrorInvalidIpAddressRange": "CIDR inválido", "rulesErrorInvalidIpAddressRange": "CIDR inválido",
"rulesErrorInvalidIpAddressRangeDescription": "Enter a valid CIDR range (e.g., 10.0.0.0/8).", "rulesErrorInvalidIpAddressRangeDescription": "Por favor, introduzca un valor CIDR válido",
"rulesErrorInvalidUrl": "Invalid path", "rulesErrorInvalidUrl": "Ruta URL inválida",
"rulesErrorInvalidUrlDescription": "Enter a valid URL path or pattern (e.g., /api/*).", "rulesErrorInvalidUrlDescription": "Por favor, introduzca un valor de ruta de URL válido",
"rulesErrorInvalidIpAddress": "Invalid IP address", "rulesErrorInvalidIpAddress": "IP inválida",
"rulesErrorInvalidIpAddressDescription": "Enter a valid IPv4 or IPv6 address.", "rulesErrorInvalidIpAddressDescription": "Por favor, introduzca una dirección IP válida",
"rulesErrorUpdate": "Error al actualizar las reglas", "rulesErrorUpdate": "Error al actualizar las reglas",
"rulesErrorUpdateDescription": "Se ha producido un error al actualizar las reglas", "rulesErrorUpdateDescription": "Se ha producido un error al actualizar las reglas",
"rulesUpdated": "Activar Reglas", "rulesUpdated": "Activar Reglas",
@@ -768,23 +765,15 @@
"rulesMatchIpAddressRangeDescription": "Introduzca una dirección en formato CIDR (por ejemplo, 103.21.244.0/22)", "rulesMatchIpAddressRangeDescription": "Introduzca una dirección en formato CIDR (por ejemplo, 103.21.244.0/22)",
"rulesMatchIpAddress": "Introduzca una dirección IP (por ejemplo, 103.21.244.12)", "rulesMatchIpAddress": "Introduzca una dirección IP (por ejemplo, 103.21.244.12)",
"rulesMatchUrl": "Introduzca una ruta URL o patrón (por ej., /api/v1/todos o /api/v1/*)", "rulesMatchUrl": "Introduzca una ruta URL o patrón (por ej., /api/v1/todos o /api/v1/*)",
"rulesErrorInvalidPriority": "Invalid priority", "rulesErrorInvalidPriority": "Prioridad inválida",
"rulesErrorInvalidPriorityDescription": "Enter a whole number of 1 or higher.", "rulesErrorInvalidPriorityDescription": "Por favor, introduzca una prioridad válida",
"rulesErrorDuplicatePriority": "Duplicate priorities", "rulesErrorDuplicatePriority": "Prioridades duplicadas",
"rulesErrorDuplicatePriorityDescription": "Each rule must have a unique priority number.", "rulesErrorDuplicatePriorityDescription": "Por favor, introduzca prioridades únicas",
"rulesErrorValidation": "Invalid rules",
"rulesErrorValidationRuleDescription": "Rule {ruleNumber}: {message}",
"rulesErrorValueRequired": "Enter a value for this rule.",
"rulesErrorInvalidCountry": "Invalid country",
"rulesErrorInvalidCountryDescription": "Select a valid country.",
"rulesErrorInvalidAsn": "Invalid ASN",
"rulesErrorInvalidAsnDescription": "Enter a valid ASN (e.g., AS15169).",
"ruleUpdated": "Reglas actualizadas", "ruleUpdated": "Reglas actualizadas",
"ruleUpdatedDescription": "Reglas actualizadas correctamente", "ruleUpdatedDescription": "Reglas actualizadas correctamente",
"ruleErrorUpdate": "Operación fallida", "ruleErrorUpdate": "Operación fallida",
"ruleErrorUpdateDescription": "Se ha producido un error durante la operación de guardado", "ruleErrorUpdateDescription": "Se ha producido un error durante la operación de guardado",
"rulesPriority": "Prioridad", "rulesPriority": "Prioridad",
"rulesReorderDragHandle": "Drag to reorder rule priority",
"rulesAction": "Accin", "rulesAction": "Accin",
"rulesMatchType": "Tipo de partida", "rulesMatchType": "Tipo de partida",
"value": "Valor", "value": "Valor",
@@ -803,7 +792,7 @@
"rulesResource": "Configuración de reglas de recursos", "rulesResource": "Configuración de reglas de recursos",
"rulesResourceDescription": "Configurar reglas para controlar el acceso al recurso", "rulesResourceDescription": "Configurar reglas para controlar el acceso al recurso",
"ruleSubmit": "Añadir Regla", "ruleSubmit": "Añadir Regla",
"rulesNoOne": "No rules yet.", "rulesNoOne": "No hay reglas. Agregue una regla usando el formulario.",
"rulesOrder": "Las reglas son evaluadas por prioridad en orden ascendente.", "rulesOrder": "Las reglas son evaluadas por prioridad en orden ascendente.",
"rulesSubmit": "Guardar Reglas", "rulesSubmit": "Guardar Reglas",
"policyErrorCreate": "Error al crear la política", "policyErrorCreate": "Error al crear la política",
@@ -814,44 +803,7 @@
"policyErrorUpdateMessageDescription": "Se ha producido un error inesperado", "policyErrorUpdateMessageDescription": "Se ha producido un error inesperado",
"policyCreatedSuccess": "Política de recursos creada con éxito", "policyCreatedSuccess": "Política de recursos creada con éxito",
"policyUpdatedSuccess": "Política de recursos actualizada con éxito", "policyUpdatedSuccess": "Política de recursos actualizada con éxito",
"authMethodsSave": "Save Settings", "authMethodsSave": "Guardar métodos de autenticación",
"policyAuthStackTitle": "Authentication",
"policyAuthStackDescription": "Control which authentication methods are required to access this resource",
"policyAuthOrLogicTitle": "Multiple authentication methods active",
"policyAuthOrLogicBanner": "Visitors may authenticate using any one of the active methods below. They do not need to complete all of them.",
"policyAuthMethodActive": "Active",
"policyAuthMethodOff": "Off",
"policyAuthSsoTitle": "Platform SSO",
"policyAuthSsoDescription": "Require sign-in through your organization's identity provider",
"policyAuthSsoSummary": "{idp} · {users} users, {roles} roles",
"policyAuthSsoDefaultIdp": "Default provider",
"policyAuthAddDefaultIdentityProvider": "Add Default Identity Provider",
"policyAuthOtherMethodsTitle": "Other Methods",
"policyAuthOtherMethodsDescription": "Optional methods visitors can use instead of or alongside platform SSO",
"policyAuthPasscodeTitle": "Passcode",
"policyAuthPasscodeDescription": "Require a shared alphanumeric passcode to access the resource",
"policyAuthPasscodeSummary": "Passcode set",
"policyAuthPincodeTitle": "PIN Code",
"policyAuthPincodeDescription": "A short numeric code required to access the resource",
"policyAuthPincodeSummary": "6-digit PIN set",
"policyAuthEmailTitle": "Email Whitelist",
"policyAuthEmailDescription": "Allow listed email addresses with one-time passwords",
"policyAuthEmailSummary": "{count} addresses allowed",
"policyAuthEmailOtpCallout": "Enabling email whitelist sends a one-time password to the visitor's email on login.",
"policyAuthHeaderAuthTitle": "Basic Header Auth",
"policyAuthHeaderAuthDescription": "Validate a custom HTTP header name and value on each request",
"policyAuthHeaderAuthSummary": "Header configured",
"policyAuthHeaderName": "Header name",
"policyAuthHeaderValue": "Expected value",
"policyAccessRulesTitle": "Access Rules",
"policyAccessRulesEnableDescription": "When enabled, rules are evaluated in descending order until one evaluates as true.",
"policyAccessRulesFirstMatch": "Rules are evaluated top to bottom. The first matching rule decides the outcome.",
"policyAccessRulesHowItWorks": "Rules match requests by path, IP address, location, or other criteria. Each rule applies an action: bypass authentication, block access, or pass to authentication. If no rule matches, traffic continues to authentication.",
"policyAccessRulesFallthroughOff": "When rules are disabled, all traffic passes through to authentication.",
"policyAccessRulesFallthroughOn": "When no rule matches, traffic passes through to authentication.",
"rulesPlaceholderCidr": "10.0.0.0/8",
"rulesPlaceholderPath": "/admin/*",
"rulesPlaceholderGeo": "RU, KP",
"rulesSave": "Guardar reglas", "rulesSave": "Guardar reglas",
"resourceErrorCreate": "Error al crear recurso", "resourceErrorCreate": "Error al crear recurso",
"resourceErrorCreateDescription": "Se ha producido un error al crear el recurso", "resourceErrorCreateDescription": "Se ha producido un error al crear el recurso",
@@ -1268,10 +1220,8 @@
"addLabels": "Agregar etiquetas", "addLabels": "Agregar etiquetas",
"siteLabelsTab": "Etiquetas", "siteLabelsTab": "Etiquetas",
"siteLabelsDescription": "Administrar las etiquetas asociadas con este sitio.", "siteLabelsDescription": "Administrar las etiquetas asociadas con este sitio.",
"labelsNotFound": "No labels found.", "labelsNotFound": "Etiquetas no encontradas",
"labelsEmptyCreateHint": "Start typing above to create a label.",
"labelSearch": "Buscar etiquetas", "labelSearch": "Buscar etiquetas",
"labelSearchOrCreate": "Search or create a label",
"accessLabelFilterCount": "{count, plural, one {# etiqueta} other {# etiquetas}}", "accessLabelFilterCount": "{count, plural, one {# etiqueta} other {# etiquetas}}",
"labelOverflowCount": "+{count, plural, one {# etiqueta} other {# etiquetas}}", "labelOverflowCount": "+{count, plural, one {# etiqueta} other {# etiquetas}}",
"accessLabelFilterClear": "Borrar filtros de etiquetas", "accessLabelFilterClear": "Borrar filtros de etiquetas",
@@ -1512,7 +1462,7 @@
"sidebarProxyResources": "Público", "sidebarProxyResources": "Público",
"sidebarClientResources": "Privado", "sidebarClientResources": "Privado",
"sidebarPolicies": "Políticas", "sidebarPolicies": "Políticas",
"sidebarResourcePolicies": "Public Resources", "sidebarResourcePolicies": "Recursos",
"sidebarAccessControl": "Control de acceso", "sidebarAccessControl": "Control de acceso",
"sidebarLogsAndAnalytics": "Registros y análisis", "sidebarLogsAndAnalytics": "Registros y análisis",
"sidebarTeam": "Equipo", "sidebarTeam": "Equipo",
@@ -1520,7 +1470,7 @@
"sidebarAdmin": "Admin", "sidebarAdmin": "Admin",
"sidebarInvitations": "Invitaciones", "sidebarInvitations": "Invitaciones",
"sidebarRoles": "Roles", "sidebarRoles": "Roles",
"sidebarShareableLinks": "Share Links", "sidebarShareableLinks": "Enlaces",
"sidebarApiKeys": "Claves API", "sidebarApiKeys": "Claves API",
"sidebarProvisioning": "Aprovisionamiento", "sidebarProvisioning": "Aprovisionamiento",
"sidebarSettings": "Ajustes", "sidebarSettings": "Ajustes",
@@ -1697,7 +1647,7 @@
"standaloneHcFilterResourceIdFallback": "Recurso {id}", "standaloneHcFilterResourceIdFallback": "Recurso {id}",
"blueprints": "Planos", "blueprints": "Planos",
"blueprintsLog": "Registro de planos", "blueprintsLog": "Registro de planos",
"blueprintsDescription": "View past blueprint applications and their results or apply a new blueprint", "blueprintsDescription": "Ver aplicaciones de plano anteriores y sus resultados",
"blueprintAdd": "Añadir plano", "blueprintAdd": "Añadir plano",
"blueprintGoBack": "Ver todos los Planos", "blueprintGoBack": "Ver todos los Planos",
"blueprintCreate": "Crear Plano", "blueprintCreate": "Crear Plano",
@@ -2077,13 +2027,13 @@
"healthCheckUnknown": "Desconocido", "healthCheckUnknown": "Desconocido",
"healthCheck": "Chequeo de salud", "healthCheck": "Chequeo de salud",
"configureHealthCheck": "Configurar Chequeo de Salud", "configureHealthCheck": "Configurar Chequeo de Salud",
"configureHealthCheckDescription": "Set up monitoring for your resource to ensure it is always available", "configureHealthCheckDescription": "Configura la monitorización de salud para {target}",
"enableHealthChecks": "Activar Chequeos de Salud", "enableHealthChecks": "Activar Chequeos de Salud",
"healthCheckDisabledStateDescription": "Cuando está deshabilitado, el sitio no realizará comprobaciones de salud y el estado se considerará desconocido.", "healthCheckDisabledStateDescription": "Cuando está deshabilitado, el sitio no realizará comprobaciones de salud y el estado se considerará desconocido.",
"enableHealthChecksDescription": "Controlar la salud de este objetivo. Puedes supervisar un punto final diferente al objetivo si es necesario.", "enableHealthChecksDescription": "Controlar la salud de este objetivo. Puedes supervisar un punto final diferente al objetivo si es necesario.",
"healthScheme": "Método", "healthScheme": "Método",
"healthSelectScheme": "Seleccionar método", "healthSelectScheme": "Seleccionar método",
"healthCheckPortInvalid": "Port must be between 1 and 65535", "healthCheckPortInvalid": "El puerto de chequeo de salud debe estar entre 1 y 65535",
"healthCheckPath": "Ruta", "healthCheckPath": "Ruta",
"healthHostname": "IP / Nombre del host", "healthHostname": "IP / Nombre del host",
"healthPort": "Puerto", "healthPort": "Puerto",
@@ -2096,7 +2046,6 @@
"requireDeviceApproval": "Requiere aprobaciones del dispositivo", "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.", "requireDeviceApprovalDescription": "Los usuarios con este rol necesitan nuevos dispositivos aprobados por un administrador antes de poder conectarse y acceder a los recursos.",
"sshSettings": "Configuración SSH", "sshSettings": "Configuración SSH",
"sshAccess": "SSH Access",
"rdpSettings": "Configuración RDP", "rdpSettings": "Configuración RDP",
"vncSettings": "Configuración VNC", "vncSettings": "Configuración VNC",
"sshServer": "Servidor SSH", "sshServer": "Servidor SSH",
@@ -2123,13 +2072,8 @@
"sshDaemonDisclaimer": "Asegúrese de que su host objetivo esté correctamente configurado para ejecutar el daemon de autenticación antes de completar esta configuración, o la provisión fallará.", "sshDaemonDisclaimer": "Asegúrese de que su host objetivo esté correctamente configurado para ejecutar el daemon de autenticación antes de completar esta configuración, o la provisión fallará.",
"sshDaemonPort": "Puerto del Daemon", "sshDaemonPort": "Puerto del Daemon",
"sshServerDestination": "Destino del Servidor", "sshServerDestination": "Destino del Servidor",
"sshServerDestinationDescription": "Configure the destination of the SSH server", "sshServerDestinationDescription": "Configure el destino y el puerto del servidor SSH",
"destination": "Destino", "destination": "Destino",
"destinationRequired": "Destination is required.",
"domainRequired": "Domain is required.",
"proxyPortRequired": "Port is required.",
"invalidPathConfiguration": "Invalid path configuration.",
"invalidRewritePathConfiguration": "Invalid rewrite path configuration.",
"bgTargetMultiSiteDisclaimer": "Seleccionar múltiples sitios permite el enrutamiento resiliente y el failover para alta disponibilidad.", "bgTargetMultiSiteDisclaimer": "Seleccionar múltiples sitios permite el enrutamiento resiliente y el failover para alta disponibilidad.",
"roleAllowSsh": "Permitir SSH", "roleAllowSsh": "Permitir SSH",
"roleAllowSshAllow": "Permitir", "roleAllowSshAllow": "Permitir",
@@ -3090,7 +3034,7 @@
"enterConfirmation": "Ingresar confirmación", "enterConfirmation": "Ingresar confirmación",
"blueprintViewDetails": "Detalles", "blueprintViewDetails": "Detalles",
"defaultIdentityProvider": "Proveedor de identidad predeterminado", "defaultIdentityProvider": "Proveedor de identidad predeterminado",
"defaultIdentityProviderDescription": "The user will be automatically redirected to this identity provider for authentication.", "defaultIdentityProviderDescription": "Cuando se selecciona un proveedor de identidad por defecto, el usuario será redirigido automáticamente al proveedor de autenticación.",
"editInternalResourceDialogNetworkSettings": "Configuración de red", "editInternalResourceDialogNetworkSettings": "Configuración de red",
"editInternalResourceDialogAccessPolicy": "Política de acceso", "editInternalResourceDialogAccessPolicy": "Política de acceso",
"editInternalResourceDialogAddRoles": "Agregar roles", "editInternalResourceDialogAddRoles": "Agregar roles",
@@ -3402,8 +3346,6 @@
"idpUnassociateQuestion": "¿Está seguro de que desea desasociar este proveedor de identidad de esta organización?", "idpUnassociateQuestion": "¿Está seguro de que desea desasociar este proveedor de identidad de esta organización?",
"idpUnassociateDescription": "Todos los usuarios asociados con este proveedor de identidad serán eliminados de esta organización, pero el proveedor de identidad continuará existiendo para otras organizaciones asociadas.", "idpUnassociateDescription": "Todos los usuarios asociados con este proveedor de identidad serán eliminados de esta organización, pero el proveedor de identidad continuará existiendo para otras organizaciones asociadas.",
"idpUnassociateConfirm": "Confirme Desasociar Proveedor de Identidad", "idpUnassociateConfirm": "Confirme Desasociar Proveedor de Identidad",
"idpConfirmDeleteAndRemoveMeFromOrg": "DELETE AND REMOVE ME FROM ORG",
"idpUnassociateAndRemoveMeFromOrg": "UNASSOCIATE AND REMOVE ME FROM ORG",
"idpUnassociateWarning": "Esto no se puede deshacer para esta organización.", "idpUnassociateWarning": "Esto no se puede deshacer para esta organización.",
"idpUnassociatedDescription": "Proveedor de identidad desasociado de esta organización con éxito", "idpUnassociatedDescription": "Proveedor de identidad desasociado de esta organización con éxito",
"idpUnassociateMenu": "Desasociar", "idpUnassociateMenu": "Desasociar",
@@ -3510,43 +3452,5 @@
"sshErrorNoTarget": "No se especificó el objetivo", "sshErrorNoTarget": "No se especificó el objetivo",
"sshErrorWebSocket": "Conexión WebSocket fallida", "sshErrorWebSocket": "Conexión WebSocket fallida",
"sshErrorAuthFailed": "Falló la autenticación", "sshErrorAuthFailed": "Falló la autenticación",
"sshErrorConnectionClosed": "La conexión se cerró antes de completar la autenticación", "sshErrorConnectionClosed": "La conexión se cerró antes de completar la autenticación"
"sitePangolinSshDescription": "Allow SSH access to resources on this site. This can be changed later.",
"browserGatewayNoResourceForDomain": "No resource found for this domain",
"browserGatewayNoTarget": "No target",
"browserGatewayConnect": "Connect",
"browserGatewayCtrlAltDel": "Ctrl+Alt+Del",
"sshErrorSignKeyFailed": "Failed to sign SSH key for PAM push authentication. Did you sign in as a user?",
"sshTerminalError": "Error: {error}",
"sshConnectionClosedCode": "Connection closed (code {code})",
"sshPrivateKeyPlaceholder": "-----BEGIN OPENSSH PRIVATE KEY-----",
"sshPrivateKeyRequired": "Private key is required",
"vncTitle": "VNC",
"vncSignInDescription": "Enter your VNC password to connect",
"vncPasswordOptional": "Password (optional)",
"vncNoResourceTarget": "No resource target is available",
"vncFailedToLoadNovnc": "Failed to load noVNC",
"vncAuthFailedStatus": "Status {status}",
"vncPasteClipboard": "Paste clipboard",
"rdpTitle": "RDP",
"rdpSignInTitle": "Sign in to Remote Desktop",
"rdpSignInDescription": "Enter Windows credentials to connect",
"rdpLoadingModule": "Loading module...",
"rdpFailedToLoadModule": "Failed to load RDP module",
"rdpNotReady": "Not ready",
"rdpModuleInitializing": "RDP module is still initializing",
"rdpDownloadingFiles": "Downloading {count} file(s) from remote…",
"rdpDownloadFailed": "Download failed: {fileName}",
"rdpUploaded": "Uploaded: {fileName}",
"rdpNoConnectionTarget": "No connection target available",
"rdpConnectionFailed": "Connection failed",
"rdpFit": "Fit",
"rdpFull": "Full",
"rdpReal": "Real",
"rdpMeta": "Meta",
"rdpUploadFiles": "Upload files",
"rdpFilesReadyToPaste": "Files ready to paste",
"rdpFilesReadyToPasteDescription": "{count} file(s) copied to remote clipboard — press Ctrl+V on the remote desktop to paste.",
"rdpUploadFailed": "Upload failed",
"rdpUnicodeKeyboardMode": "Unicode keyboard mode"
} }
+27 -123
View File
@@ -101,8 +101,6 @@
"sitesTableViewPrivateResources": "Voir les ressources privées", "sitesTableViewPrivateResources": "Voir les ressources privées",
"siteInstallNewt": "Installer Newt", "siteInstallNewt": "Installer Newt",
"siteInstallNewtDescription": "Faites fonctionner Newt sur votre système", "siteInstallNewtDescription": "Faites fonctionner Newt sur votre système",
"siteInstallKubernetesDocsDescription": "For more and up to date Kubernetes installation information, see <docsLink>docs.pangolin.net/manage/sites/install-kubernetes</docsLink>.",
"siteInstallAdvantechDocsDescription": "For Advantech modem installation instructions, see <docsLink>docs.pangolin.net/manage/sites/install-advantech</docsLink>.",
"WgConfiguration": "Configuration WireGuard", "WgConfiguration": "Configuration WireGuard",
"WgConfigurationDescription": "Utilisez la configuration suivante pour vous connecter au réseau", "WgConfigurationDescription": "Utilisez la configuration suivante pour vous connecter au réseau",
"operatingSystem": "Système d'exploitation", "operatingSystem": "Système d'exploitation",
@@ -202,8 +200,8 @@
"shareErrorSelectResource": "Veuillez sélectionner une ressource", "shareErrorSelectResource": "Veuillez sélectionner une ressource",
"proxyResourceTitle": "Gérer les ressources publiques", "proxyResourceTitle": "Gérer les ressources publiques",
"proxyResourceDescription": "Créer et gérer des ressources accessibles au public via un navigateur web", "proxyResourceDescription": "Créer et gérer des ressources accessibles au public via un navigateur web",
"publicResourcesBannerTitle": "Web-based Public Access", "publicResourcesBannerTitle": "Accès public basé sur le Web",
"publicResourcesBannerDescription": "Public resources are HTTPS proxies accessible to anyone on the internet through a web browser. Unlike private resources, they do not require client-side software and can include identity and context-aware access policies.", "publicResourcesBannerDescription": "Les ressources publiques sont des proxys HTTPS ou TCP/UDP accessibles par tout le monde sur Internet via un navigateur Web. Contrairement aux ressources privées, elles n'exigent pas de logiciel côté client et peuvent inclure des politiques d'accès basées sur l'identité et le contexte.",
"clientResourceTitle": "Gérer les ressources privées", "clientResourceTitle": "Gérer les ressources privées",
"clientResourceDescription": "Créer et gérer des ressources qui ne sont accessibles que via un client connecté", "clientResourceDescription": "Créer et gérer des ressources qui ne sont accessibles que via un client connecté",
"privateResourcesBannerTitle": "Accès privé sans confiance", "privateResourcesBannerTitle": "Accès privé sans confiance",
@@ -211,16 +209,15 @@
"resourcesSearch": "Chercher des ressources...", "resourcesSearch": "Chercher des ressources...",
"resourceAdd": "Ajouter une ressource", "resourceAdd": "Ajouter une ressource",
"resourceErrorDelte": "Erreur lors de la de suppression de la ressource", "resourceErrorDelte": "Erreur lors de la de suppression de la ressource",
"resourcePoliciesTitle": "Manage Public Resource Policies", "resourcePoliciesTitle": "Gérer les politiques de ressource",
"resourcePoliciesAttachedResourcesColumnTitle": "Resources", "resourcePoliciesAttachedResourcesColumnTitle": "Ressources attachées",
"resourcePoliciesAttachedResources": "{count} ressource(s)", "resourcePoliciesAttachedResources": "{count} ressource(s)",
"resourcePoliciesAttachedResourcesCount": "{count, plural, one {# resource} other {# resources}}",
"resourcePoliciesAttachedResourcesEmpty": "pas de ressources", "resourcePoliciesAttachedResourcesEmpty": "pas de ressources",
"resourcePoliciesDescription": "Create and manage authentication policies to control access to your public resources", "resourcePoliciesDescription": "Créer et gérer des politiques d'authentification pour contrôler l'accès à vos ressources",
"resourcePoliciesSearch": "Chercher des politiques...", "resourcePoliciesSearch": "Chercher des politiques...",
"resourcePoliciesAdd": "Ajouter une politique", "resourcePoliciesAdd": "Ajouter une politique",
"resourcePoliciesDefaultBadgeText": "Politique par défaut", "resourcePoliciesDefaultBadgeText": "Politique par défaut",
"resourcePoliciesCreate": "Create Public Resource Policy", "resourcePoliciesCreate": "Créer une politique de ressource",
"resourcePoliciesCreateDescription": "Suivez les étapes ci-dessous pour créer une nouvelle politique", "resourcePoliciesCreateDescription": "Suivez les étapes ci-dessous pour créer une nouvelle politique",
"resourcePolicyName": "Nom de la politique", "resourcePolicyName": "Nom de la politique",
"resourcePolicyNameDescription": "Donnez à cette politique un nom pour l'identifier parmi vos ressources", "resourcePolicyNameDescription": "Donnez à cette politique un nom pour l'identifier parmi vos ressources",
@@ -314,7 +311,7 @@
"rules": "Règles", "rules": "Règles",
"resourceSettingDescription": "Configurer les paramètres de la ressource", "resourceSettingDescription": "Configurer les paramètres de la ressource",
"resourceSetting": "Réglages de {resourceName}", "resourceSetting": "Réglages de {resourceName}",
"resourcePolicySettingDescription": "Configure the settings on this public resource policy", "resourcePolicySettingDescription": "Configurer les paramètres de la politique de ressource",
"resourcePolicySetting": "Paramètres de {policyName}", "resourcePolicySetting": "Paramètres de {policyName}",
"alwaysAllow": "Outrepasser l'authentification", "alwaysAllow": "Outrepasser l'authentification",
"alwaysDeny": "Bloquer l'accès", "alwaysDeny": "Bloquer l'accès",
@@ -756,11 +753,11 @@
"rulesErrorDuplicate": "Règle en double", "rulesErrorDuplicate": "Règle en double",
"rulesErrorDuplicateDescription": "Une règle avec ces paramètres existe déjà", "rulesErrorDuplicateDescription": "Une règle avec ces paramètres existe déjà",
"rulesErrorInvalidIpAddressRange": "CIDR invalide", "rulesErrorInvalidIpAddressRange": "CIDR invalide",
"rulesErrorInvalidIpAddressRangeDescription": "Enter a valid CIDR range (e.g., 10.0.0.0/8).", "rulesErrorInvalidIpAddressRangeDescription": "Veuillez entrer une valeur CIDR valide",
"rulesErrorInvalidUrl": "Invalid path", "rulesErrorInvalidUrl": "Chemin URL invalide",
"rulesErrorInvalidUrlDescription": "Enter a valid URL path or pattern (e.g., /api/*).", "rulesErrorInvalidUrlDescription": "Veuillez entrer un chemin URL valide",
"rulesErrorInvalidIpAddress": "Invalid IP address", "rulesErrorInvalidIpAddress": "IP invalide",
"rulesErrorInvalidIpAddressDescription": "Enter a valid IPv4 or IPv6 address.", "rulesErrorInvalidIpAddressDescription": "Veuillez entrer une adresse IP valide",
"rulesErrorUpdate": "Échec de la mise à jour des règles", "rulesErrorUpdate": "Échec de la mise à jour des règles",
"rulesErrorUpdateDescription": "Une erreur s'est produite lors de la mise à jour des règles", "rulesErrorUpdateDescription": "Une erreur s'est produite lors de la mise à jour des règles",
"rulesUpdated": "Activer les règles", "rulesUpdated": "Activer les règles",
@@ -768,23 +765,15 @@
"rulesMatchIpAddressRangeDescription": "Entrez une adresse au format CIDR (ex: 103.21.244.0/22)", "rulesMatchIpAddressRangeDescription": "Entrez une adresse au format CIDR (ex: 103.21.244.0/22)",
"rulesMatchIpAddress": "Entrez une adresse IP (ex: 103.21.244.12)", "rulesMatchIpAddress": "Entrez une adresse IP (ex: 103.21.244.12)",
"rulesMatchUrl": "Entrez un chemin URL ou un motif (ex: /api/v1/todos ou /api/v1/*)", "rulesMatchUrl": "Entrez un chemin URL ou un motif (ex: /api/v1/todos ou /api/v1/*)",
"rulesErrorInvalidPriority": "Invalid priority", "rulesErrorInvalidPriority": "Priorité invalide",
"rulesErrorInvalidPriorityDescription": "Enter a whole number of 1 or higher.", "rulesErrorInvalidPriorityDescription": "Veuillez entrer une priorité valide",
"rulesErrorDuplicatePriority": "Duplicate priorities", "rulesErrorDuplicatePriority": "Priorités en double",
"rulesErrorDuplicatePriorityDescription": "Each rule must have a unique priority number.", "rulesErrorDuplicatePriorityDescription": "Veuillez entrer des priorités uniques",
"rulesErrorValidation": "Invalid rules",
"rulesErrorValidationRuleDescription": "Rule {ruleNumber}: {message}",
"rulesErrorValueRequired": "Enter a value for this rule.",
"rulesErrorInvalidCountry": "Invalid country",
"rulesErrorInvalidCountryDescription": "Select a valid country.",
"rulesErrorInvalidAsn": "Invalid ASN",
"rulesErrorInvalidAsnDescription": "Enter a valid ASN (e.g., AS15169).",
"ruleUpdated": "Règles mises à jour", "ruleUpdated": "Règles mises à jour",
"ruleUpdatedDescription": "Règles mises à jour avec succès", "ruleUpdatedDescription": "Règles mises à jour avec succès",
"ruleErrorUpdate": "L'opération a échoué", "ruleErrorUpdate": "L'opération a échoué",
"ruleErrorUpdateDescription": "Une erreur s'est produite lors de l'enregistrement", "ruleErrorUpdateDescription": "Une erreur s'est produite lors de l'enregistrement",
"rulesPriority": "Priorité", "rulesPriority": "Priorité",
"rulesReorderDragHandle": "Drag to reorder rule priority",
"rulesAction": "Action", "rulesAction": "Action",
"rulesMatchType": "Type de correspondance", "rulesMatchType": "Type de correspondance",
"value": "Valeur", "value": "Valeur",
@@ -803,7 +792,7 @@
"rulesResource": "Configuration des règles de ressource", "rulesResource": "Configuration des règles de ressource",
"rulesResourceDescription": "Configurer les règles pour contrôler l'accès à la ressource", "rulesResourceDescription": "Configurer les règles pour contrôler l'accès à la ressource",
"ruleSubmit": "Ajouter une règle", "ruleSubmit": "Ajouter une règle",
"rulesNoOne": "No rules yet.", "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.", "rulesOrder": "Les règles sont évaluées par priorité dans l'ordre croissant.",
"rulesSubmit": "Enregistrer les règles", "rulesSubmit": "Enregistrer les règles",
"policyErrorCreate": "Erreur lors de la création de la politique", "policyErrorCreate": "Erreur lors de la création de la politique",
@@ -814,44 +803,7 @@
"policyErrorUpdateMessageDescription": "Une erreur inattendue s'est produite", "policyErrorUpdateMessageDescription": "Une erreur inattendue s'est produite",
"policyCreatedSuccess": "Politique de ressource créée avec succès", "policyCreatedSuccess": "Politique de ressource créée avec succès",
"policyUpdatedSuccess": "Politique de ressource mise à jour avec succès", "policyUpdatedSuccess": "Politique de ressource mise à jour avec succès",
"authMethodsSave": "Save Settings", "authMethodsSave": "Enregistrer les méthodes d'authentification",
"policyAuthStackTitle": "Authentication",
"policyAuthStackDescription": "Control which authentication methods are required to access this resource",
"policyAuthOrLogicTitle": "Multiple authentication methods active",
"policyAuthOrLogicBanner": "Visitors may authenticate using any one of the active methods below. They do not need to complete all of them.",
"policyAuthMethodActive": "Active",
"policyAuthMethodOff": "Off",
"policyAuthSsoTitle": "Platform SSO",
"policyAuthSsoDescription": "Require sign-in through your organization's identity provider",
"policyAuthSsoSummary": "{idp} · {users} users, {roles} roles",
"policyAuthSsoDefaultIdp": "Default provider",
"policyAuthAddDefaultIdentityProvider": "Add Default Identity Provider",
"policyAuthOtherMethodsTitle": "Other Methods",
"policyAuthOtherMethodsDescription": "Optional methods visitors can use instead of or alongside platform SSO",
"policyAuthPasscodeTitle": "Passcode",
"policyAuthPasscodeDescription": "Require a shared alphanumeric passcode to access the resource",
"policyAuthPasscodeSummary": "Passcode set",
"policyAuthPincodeTitle": "PIN Code",
"policyAuthPincodeDescription": "A short numeric code required to access the resource",
"policyAuthPincodeSummary": "6-digit PIN set",
"policyAuthEmailTitle": "Email Whitelist",
"policyAuthEmailDescription": "Allow listed email addresses with one-time passwords",
"policyAuthEmailSummary": "{count} addresses allowed",
"policyAuthEmailOtpCallout": "Enabling email whitelist sends a one-time password to the visitor's email on login.",
"policyAuthHeaderAuthTitle": "Basic Header Auth",
"policyAuthHeaderAuthDescription": "Validate a custom HTTP header name and value on each request",
"policyAuthHeaderAuthSummary": "Header configured",
"policyAuthHeaderName": "Header name",
"policyAuthHeaderValue": "Expected value",
"policyAccessRulesTitle": "Access Rules",
"policyAccessRulesEnableDescription": "When enabled, rules are evaluated in descending order until one evaluates as true.",
"policyAccessRulesFirstMatch": "Rules are evaluated top to bottom. The first matching rule decides the outcome.",
"policyAccessRulesHowItWorks": "Rules match requests by path, IP address, location, or other criteria. Each rule applies an action: bypass authentication, block access, or pass to authentication. If no rule matches, traffic continues to authentication.",
"policyAccessRulesFallthroughOff": "When rules are disabled, all traffic passes through to authentication.",
"policyAccessRulesFallthroughOn": "When no rule matches, traffic passes through to authentication.",
"rulesPlaceholderCidr": "10.0.0.0/8",
"rulesPlaceholderPath": "/admin/*",
"rulesPlaceholderGeo": "RU, KP",
"rulesSave": "Enregistrer les règles", "rulesSave": "Enregistrer les règles",
"resourceErrorCreate": "Erreur lors de la création de la ressource", "resourceErrorCreate": "Erreur lors de la création de la ressource",
"resourceErrorCreateDescription": "Une erreur s'est produite lors de la création de la ressource", "resourceErrorCreateDescription": "Une erreur s'est produite lors de la création de la ressource",
@@ -1268,10 +1220,8 @@
"addLabels": "Ajouter des étiquettes", "addLabels": "Ajouter des étiquettes",
"siteLabelsTab": "Étiquettes", "siteLabelsTab": "Étiquettes",
"siteLabelsDescription": "Gérer les étiquettes associées à ce site.", "siteLabelsDescription": "Gérer les étiquettes associées à ce site.",
"labelsNotFound": "No labels found.", "labelsNotFound": "Étiquettes introuvables",
"labelsEmptyCreateHint": "Start typing above to create a label.",
"labelSearch": "Chercher des étiquettes", "labelSearch": "Chercher des étiquettes",
"labelSearchOrCreate": "Search or create a label",
"accessLabelFilterCount": "{count, plural, one {# étiquette} other {# étiquettes}}", "accessLabelFilterCount": "{count, plural, one {# étiquette} other {# étiquettes}}",
"labelOverflowCount": "+{count, plural, one {# étiquette} other {# étiquettes}}", "labelOverflowCount": "+{count, plural, one {# étiquette} other {# étiquettes}}",
"accessLabelFilterClear": "Effacer les filtres d'étiquette", "accessLabelFilterClear": "Effacer les filtres d'étiquette",
@@ -1512,7 +1462,7 @@
"sidebarProxyResources": "Publique", "sidebarProxyResources": "Publique",
"sidebarClientResources": "Privé", "sidebarClientResources": "Privé",
"sidebarPolicies": "Politiques", "sidebarPolicies": "Politiques",
"sidebarResourcePolicies": "Public Resources", "sidebarResourcePolicies": "Ressources",
"sidebarAccessControl": "Contrôle d'accès", "sidebarAccessControl": "Contrôle d'accès",
"sidebarLogsAndAnalytics": "Journaux & Analytiques", "sidebarLogsAndAnalytics": "Journaux & Analytiques",
"sidebarTeam": "Equipe", "sidebarTeam": "Equipe",
@@ -1520,7 +1470,7 @@
"sidebarAdmin": "Administrateur", "sidebarAdmin": "Administrateur",
"sidebarInvitations": "Invitations", "sidebarInvitations": "Invitations",
"sidebarRoles": "Rôles", "sidebarRoles": "Rôles",
"sidebarShareableLinks": "Share Links", "sidebarShareableLinks": "Liens",
"sidebarApiKeys": "Clés API", "sidebarApiKeys": "Clés API",
"sidebarProvisioning": "Mise en place", "sidebarProvisioning": "Mise en place",
"sidebarSettings": "Réglages", "sidebarSettings": "Réglages",
@@ -1697,7 +1647,7 @@
"standaloneHcFilterResourceIdFallback": "Ressource {id}", "standaloneHcFilterResourceIdFallback": "Ressource {id}",
"blueprints": "Configs", "blueprints": "Configs",
"blueprintsLog": "Journal des plans", "blueprintsLog": "Journal des plans",
"blueprintsDescription": "View past blueprint applications and their results or apply a new blueprint", "blueprintsDescription": "Voir les applications passées des plans et leurs résultats",
"blueprintAdd": "Ajouter une Config", "blueprintAdd": "Ajouter une Config",
"blueprintGoBack": "Voir toutes les Configs", "blueprintGoBack": "Voir toutes les Configs",
"blueprintCreate": "Créer une Config", "blueprintCreate": "Créer une Config",
@@ -2077,13 +2027,13 @@
"healthCheckUnknown": "Inconnu", "healthCheckUnknown": "Inconnu",
"healthCheck": "Vérification de l'état de santé", "healthCheck": "Vérification de l'état de santé",
"configureHealthCheck": "Configurer la vérification de l'état de santé", "configureHealthCheck": "Configurer la vérification de l'état de santé",
"configureHealthCheckDescription": "Set up monitoring for your resource to ensure it is always available", "configureHealthCheckDescription": "Configurer la surveillance de la santé pour {target}",
"enableHealthChecks": "Activer les vérifications de santé", "enableHealthChecks": "Activer les vérifications de santé",
"healthCheckDisabledStateDescription": "Lorsqu'il est désactivé, le site ne procédera pas aux vérifications de santé et l'état sera considéré comme inconnu.", "healthCheckDisabledStateDescription": "Lorsqu'il est désactivé, le site ne procédera pas aux vérifications de santé et l'état sera considéré comme inconnu.",
"enableHealthChecksDescription": "Surveiller la vie de cette cible. Vous pouvez surveiller un point de terminaison différent de la cible si nécessaire.", "enableHealthChecksDescription": "Surveiller la vie de cette cible. Vous pouvez surveiller un point de terminaison différent de la cible si nécessaire.",
"healthScheme": "Méthode", "healthScheme": "Méthode",
"healthSelectScheme": "Sélectionnez la méthode", "healthSelectScheme": "Sélectionnez la méthode",
"healthCheckPortInvalid": "Port must be between 1 and 65535", "healthCheckPortInvalid": "Le port du bilan de santé doit être compris entre 1 et 65535",
"healthCheckPath": "Chemin d'accès", "healthCheckPath": "Chemin d'accès",
"healthHostname": "IP / Hôte", "healthHostname": "IP / Hôte",
"healthPort": "Port", "healthPort": "Port",
@@ -2096,7 +2046,6 @@
"requireDeviceApproval": "Exiger les autorisations de l'appareil", "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.", "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.",
"sshSettings": "Paramètres SSH", "sshSettings": "Paramètres SSH",
"sshAccess": "SSH Access",
"rdpSettings": "Paramètres RDP", "rdpSettings": "Paramètres RDP",
"vncSettings": "Paramètres VNC", "vncSettings": "Paramètres VNC",
"sshServer": "Serveur SSH", "sshServer": "Serveur SSH",
@@ -2123,13 +2072,8 @@
"sshDaemonDisclaimer": "Assurez-vous que votre hôte cible est correctement configuré pour exécuter le daemon auth avant de terminer cette configuration, ou l'approvisionnement échouera.", "sshDaemonDisclaimer": "Assurez-vous que votre hôte cible est correctement configuré pour exécuter le daemon auth avant de terminer cette configuration, ou l'approvisionnement échouera.",
"sshDaemonPort": "Port du Démon", "sshDaemonPort": "Port du Démon",
"sshServerDestination": "Destination du Serveur", "sshServerDestination": "Destination du Serveur",
"sshServerDestinationDescription": "Configure the destination of the SSH server", "sshServerDestinationDescription": "Configurer la destination et le port du serveur SSH",
"destination": "Destination", "destination": "Destination",
"destinationRequired": "Destination is required.",
"domainRequired": "Domain is required.",
"proxyPortRequired": "Port is required.",
"invalidPathConfiguration": "Invalid path configuration.",
"invalidRewritePathConfiguration": "Invalid rewrite path configuration.",
"bgTargetMultiSiteDisclaimer": "La sélection de plusieurs sites permet un routage résilient et une bascule pour une haute disponibilité.", "bgTargetMultiSiteDisclaimer": "La sélection de plusieurs sites permet un routage résilient et une bascule pour une haute disponibilité.",
"roleAllowSsh": "Autoriser SSH", "roleAllowSsh": "Autoriser SSH",
"roleAllowSshAllow": "Autoriser", "roleAllowSshAllow": "Autoriser",
@@ -3090,7 +3034,7 @@
"enterConfirmation": "Entrez la confirmation", "enterConfirmation": "Entrez la confirmation",
"blueprintViewDetails": "Détails", "blueprintViewDetails": "Détails",
"defaultIdentityProvider": "Fournisseur d'identité par défaut", "defaultIdentityProvider": "Fournisseur d'identité par défaut",
"defaultIdentityProviderDescription": "The user will be automatically redirected to this identity provider for authentication.", "defaultIdentityProviderDescription": "Lorsqu'un fournisseur d'identité par défaut est sélectionné, l'utilisateur sera automatiquement redirigé vers le fournisseur pour authentification.",
"editInternalResourceDialogNetworkSettings": "Paramètres réseau", "editInternalResourceDialogNetworkSettings": "Paramètres réseau",
"editInternalResourceDialogAccessPolicy": "Politique d'accès", "editInternalResourceDialogAccessPolicy": "Politique d'accès",
"editInternalResourceDialogAddRoles": "Ajouter des rôles", "editInternalResourceDialogAddRoles": "Ajouter des rôles",
@@ -3402,8 +3346,6 @@
"idpUnassociateQuestion": "Êtes-vous sûr de vouloir dissocier ce fournisseur d'identités de cette organisation?", "idpUnassociateQuestion": "Êtes-vous sûr de vouloir dissocier ce fournisseur d'identités de cette organisation?",
"idpUnassociateDescription": "Tous les utilisateurs associés à ce fournisseur d'identités seront retirés de cette organisation, mais le fournisseur d'identités continuera d'exister pour d'autres organisations associées.", "idpUnassociateDescription": "Tous les utilisateurs associés à ce fournisseur d'identités seront retirés de cette organisation, mais le fournisseur d'identités continuera d'exister pour d'autres organisations associées.",
"idpUnassociateConfirm": "Confirmer la dissociation du fournisseur d'identités", "idpUnassociateConfirm": "Confirmer la dissociation du fournisseur d'identités",
"idpConfirmDeleteAndRemoveMeFromOrg": "DELETE AND REMOVE ME FROM ORG",
"idpUnassociateAndRemoveMeFromOrg": "UNASSOCIATE AND REMOVE ME FROM ORG",
"idpUnassociateWarning": "Cela ne peut pas être annulé pour cette organisation.", "idpUnassociateWarning": "Cela ne peut pas être annulé pour cette organisation.",
"idpUnassociatedDescription": "Fournisseur d'identités dissocié de cette organisation avec succès", "idpUnassociatedDescription": "Fournisseur d'identités dissocié de cette organisation avec succès",
"idpUnassociateMenu": "Dissocier", "idpUnassociateMenu": "Dissocier",
@@ -3510,43 +3452,5 @@
"sshErrorNoTarget": "Aucune cible spécifiée", "sshErrorNoTarget": "Aucune cible spécifiée",
"sshErrorWebSocket": "Échec de la connexion WebSocket", "sshErrorWebSocket": "Échec de la connexion WebSocket",
"sshErrorAuthFailed": "Échec de l'authentification", "sshErrorAuthFailed": "Échec de l'authentification",
"sshErrorConnectionClosed": "Connexion fermée avant que l'authentification soit terminée", "sshErrorConnectionClosed": "Connexion fermée avant que l'authentification soit terminée"
"sitePangolinSshDescription": "Allow SSH access to resources on this site. This can be changed later.",
"browserGatewayNoResourceForDomain": "No resource found for this domain",
"browserGatewayNoTarget": "No target",
"browserGatewayConnect": "Connect",
"browserGatewayCtrlAltDel": "Ctrl+Alt+Del",
"sshErrorSignKeyFailed": "Failed to sign SSH key for PAM push authentication. Did you sign in as a user?",
"sshTerminalError": "Error: {error}",
"sshConnectionClosedCode": "Connection closed (code {code})",
"sshPrivateKeyPlaceholder": "-----BEGIN OPENSSH PRIVATE KEY-----",
"sshPrivateKeyRequired": "Private key is required",
"vncTitle": "VNC",
"vncSignInDescription": "Enter your VNC password to connect",
"vncPasswordOptional": "Password (optional)",
"vncNoResourceTarget": "No resource target is available",
"vncFailedToLoadNovnc": "Failed to load noVNC",
"vncAuthFailedStatus": "Status {status}",
"vncPasteClipboard": "Paste clipboard",
"rdpTitle": "RDP",
"rdpSignInTitle": "Sign in to Remote Desktop",
"rdpSignInDescription": "Enter Windows credentials to connect",
"rdpLoadingModule": "Loading module...",
"rdpFailedToLoadModule": "Failed to load RDP module",
"rdpNotReady": "Not ready",
"rdpModuleInitializing": "RDP module is still initializing",
"rdpDownloadingFiles": "Downloading {count} file(s) from remote…",
"rdpDownloadFailed": "Download failed: {fileName}",
"rdpUploaded": "Uploaded: {fileName}",
"rdpNoConnectionTarget": "No connection target available",
"rdpConnectionFailed": "Connection failed",
"rdpFit": "Fit",
"rdpFull": "Full",
"rdpReal": "Real",
"rdpMeta": "Meta",
"rdpUploadFiles": "Upload files",
"rdpFilesReadyToPaste": "Files ready to paste",
"rdpFilesReadyToPasteDescription": "{count} file(s) copied to remote clipboard — press Ctrl+V on the remote desktop to paste.",
"rdpUploadFailed": "Upload failed",
"rdpUnicodeKeyboardMode": "Unicode keyboard mode"
} }
+27 -123
View File
@@ -101,8 +101,6 @@
"sitesTableViewPrivateResources": "Visualizza Risorse Private", "sitesTableViewPrivateResources": "Visualizza Risorse Private",
"siteInstallNewt": "Installa Newt", "siteInstallNewt": "Installa Newt",
"siteInstallNewtDescription": "Esegui Newt sul tuo sistema", "siteInstallNewtDescription": "Esegui Newt sul tuo sistema",
"siteInstallKubernetesDocsDescription": "For more and up to date Kubernetes installation information, see <docsLink>docs.pangolin.net/manage/sites/install-kubernetes</docsLink>.",
"siteInstallAdvantechDocsDescription": "For Advantech modem installation instructions, see <docsLink>docs.pangolin.net/manage/sites/install-advantech</docsLink>.",
"WgConfiguration": "Configurazione WireGuard", "WgConfiguration": "Configurazione WireGuard",
"WgConfigurationDescription": "Utilizzare la seguente configurazione per connettersi alla rete", "WgConfigurationDescription": "Utilizzare la seguente configurazione per connettersi alla rete",
"operatingSystem": "Sistema Operativo", "operatingSystem": "Sistema Operativo",
@@ -202,8 +200,8 @@
"shareErrorSelectResource": "Seleziona una risorsa", "shareErrorSelectResource": "Seleziona una risorsa",
"proxyResourceTitle": "Gestisci Risorse Pubbliche", "proxyResourceTitle": "Gestisci Risorse Pubbliche",
"proxyResourceDescription": "Creare e gestire risorse pubbliche accessibili tramite un browser web", "proxyResourceDescription": "Creare e gestire risorse pubbliche accessibili tramite un browser web",
"publicResourcesBannerTitle": "Web-based Public Access", "publicResourcesBannerTitle": "Accesso Pubblico Basato sul Web",
"publicResourcesBannerDescription": "Public resources are HTTPS proxies accessible to anyone on the internet through a web browser. Unlike private resources, they do not require client-side software and can include identity and context-aware access policies.", "publicResourcesBannerDescription": "Le risorse pubbliche sono proxy HTTPS o TCP/UDP accessibili da chiunque tramite Internet da un browser web. A differenza delle risorse private non richiedono software lato client e possono includere politiche di accesso basate su identità e contesto.",
"clientResourceTitle": "Gestisci Risorse Private", "clientResourceTitle": "Gestisci Risorse Private",
"clientResourceDescription": "Crea e gestisci risorse accessibili solo tramite un client connesso", "clientResourceDescription": "Crea e gestisci risorse accessibili solo tramite un client connesso",
"privateResourcesBannerTitle": "Accesso Privato Zero-Trust", "privateResourcesBannerTitle": "Accesso Privato Zero-Trust",
@@ -211,16 +209,15 @@
"resourcesSearch": "Cerca risorse...", "resourcesSearch": "Cerca risorse...",
"resourceAdd": "Aggiungi Risorsa", "resourceAdd": "Aggiungi Risorsa",
"resourceErrorDelte": "Errore nell'eliminare la risorsa", "resourceErrorDelte": "Errore nell'eliminare la risorsa",
"resourcePoliciesTitle": "Manage Public Resource Policies", "resourcePoliciesTitle": "Gestisci Politiche sulle Risorse",
"resourcePoliciesAttachedResourcesColumnTitle": "Resources", "resourcePoliciesAttachedResourcesColumnTitle": "Risorse collegate",
"resourcePoliciesAttachedResources": "{count} risorsa(e)", "resourcePoliciesAttachedResources": "{count} risorsa(e)",
"resourcePoliciesAttachedResourcesCount": "{count, plural, one {# resource} other {# resources}}",
"resourcePoliciesAttachedResourcesEmpty": "nessuna risorsa", "resourcePoliciesAttachedResourcesEmpty": "nessuna risorsa",
"resourcePoliciesDescription": "Create and manage authentication policies to control access to your public resources", "resourcePoliciesDescription": "Crea e gestisci le politiche di autenticazione per controllare l'accesso alle tue risorse",
"resourcePoliciesSearch": "Cerca politiche...", "resourcePoliciesSearch": "Cerca politiche...",
"resourcePoliciesAdd": "Aggiungi Politica", "resourcePoliciesAdd": "Aggiungi Politica",
"resourcePoliciesDefaultBadgeText": "Politica Predefinita", "resourcePoliciesDefaultBadgeText": "Politica Predefinita",
"resourcePoliciesCreate": "Create Public Resource Policy", "resourcePoliciesCreate": "Crea Politica Risorse",
"resourcePoliciesCreateDescription": "Segui i passaggi seguenti per creare una nuova politica", "resourcePoliciesCreateDescription": "Segui i passaggi seguenti per creare una nuova politica",
"resourcePolicyName": "Nome Politica", "resourcePolicyName": "Nome Politica",
"resourcePolicyNameDescription": "Dai un nome a questa politica per identificarla tra le tue risorse", "resourcePolicyNameDescription": "Dai un nome a questa politica per identificarla tra le tue risorse",
@@ -314,7 +311,7 @@
"rules": "Regole", "rules": "Regole",
"resourceSettingDescription": "Configura le impostazioni sulla risorsa", "resourceSettingDescription": "Configura le impostazioni sulla risorsa",
"resourceSetting": "Impostazioni {resourceName}", "resourceSetting": "Impostazioni {resourceName}",
"resourcePolicySettingDescription": "Configure the settings on this public resource policy", "resourcePolicySettingDescription": "Configura le impostazioni sulla politica delle risorse",
"resourcePolicySetting": "Impostazioni del sito {policyName}", "resourcePolicySetting": "Impostazioni del sito {policyName}",
"alwaysAllow": "Bypass Autenticazione", "alwaysAllow": "Bypass Autenticazione",
"alwaysDeny": "Blocca Accesso", "alwaysDeny": "Blocca Accesso",
@@ -756,11 +753,11 @@
"rulesErrorDuplicate": "Regola duplicata", "rulesErrorDuplicate": "Regola duplicata",
"rulesErrorDuplicateDescription": "Esiste già una regola con queste impostazioni", "rulesErrorDuplicateDescription": "Esiste già una regola con queste impostazioni",
"rulesErrorInvalidIpAddressRange": "CIDR non valido", "rulesErrorInvalidIpAddressRange": "CIDR non valido",
"rulesErrorInvalidIpAddressRangeDescription": "Enter a valid CIDR range (e.g., 10.0.0.0/8).", "rulesErrorInvalidIpAddressRangeDescription": "Inserisci un valore CIDR valido",
"rulesErrorInvalidUrl": "Invalid path", "rulesErrorInvalidUrl": "Percorso URL non valido",
"rulesErrorInvalidUrlDescription": "Enter a valid URL path or pattern (e.g., /api/*).", "rulesErrorInvalidUrlDescription": "Inserisci un valore di percorso URL valido",
"rulesErrorInvalidIpAddress": "Invalid IP address", "rulesErrorInvalidIpAddress": "IP non valido",
"rulesErrorInvalidIpAddressDescription": "Enter a valid IPv4 or IPv6 address.", "rulesErrorInvalidIpAddressDescription": "Inserisci un indirizzo IP valido",
"rulesErrorUpdate": "Impossibile aggiornare le regole", "rulesErrorUpdate": "Impossibile aggiornare le regole",
"rulesErrorUpdateDescription": "Si è verificato un errore durante l'aggiornamento delle regole", "rulesErrorUpdateDescription": "Si è verificato un errore durante l'aggiornamento delle regole",
"rulesUpdated": "Abilita Regole", "rulesUpdated": "Abilita Regole",
@@ -768,23 +765,15 @@
"rulesMatchIpAddressRangeDescription": "Inserisci un indirizzo in formato CIDR (es. 103.21.244.0/22)", "rulesMatchIpAddressRangeDescription": "Inserisci un indirizzo in formato CIDR (es. 103.21.244.0/22)",
"rulesMatchIpAddress": "Inserisci un indirizzo IP (es. 103.21.244.12)", "rulesMatchIpAddress": "Inserisci un indirizzo IP (es. 103.21.244.12)",
"rulesMatchUrl": "Inserisci un percorso URL o pattern (es. /api/v1/todos o /api/v1/*)", "rulesMatchUrl": "Inserisci un percorso URL o pattern (es. /api/v1/todos o /api/v1/*)",
"rulesErrorInvalidPriority": "Invalid priority", "rulesErrorInvalidPriority": "Priorità Non Valida",
"rulesErrorInvalidPriorityDescription": "Enter a whole number of 1 or higher.", "rulesErrorInvalidPriorityDescription": "Inserisci una priorità valida",
"rulesErrorDuplicatePriority": "Duplicate priorities", "rulesErrorDuplicatePriority": "Priorità Duplicate",
"rulesErrorDuplicatePriorityDescription": "Each rule must have a unique priority number.", "rulesErrorDuplicatePriorityDescription": "Inserisci priorità uniche",
"rulesErrorValidation": "Invalid rules",
"rulesErrorValidationRuleDescription": "Rule {ruleNumber}: {message}",
"rulesErrorValueRequired": "Enter a value for this rule.",
"rulesErrorInvalidCountry": "Invalid country",
"rulesErrorInvalidCountryDescription": "Select a valid country.",
"rulesErrorInvalidAsn": "Invalid ASN",
"rulesErrorInvalidAsnDescription": "Enter a valid ASN (e.g., AS15169).",
"ruleUpdated": "Regole aggiornate", "ruleUpdated": "Regole aggiornate",
"ruleUpdatedDescription": "Regole aggiornate con successo", "ruleUpdatedDescription": "Regole aggiornate con successo",
"ruleErrorUpdate": "Operazione fallita", "ruleErrorUpdate": "Operazione fallita",
"ruleErrorUpdateDescription": "Si è verificato un errore durante il salvataggio", "ruleErrorUpdateDescription": "Si è verificato un errore durante il salvataggio",
"rulesPriority": "Priorità", "rulesPriority": "Priorità",
"rulesReorderDragHandle": "Drag to reorder rule priority",
"rulesAction": "Azione", "rulesAction": "Azione",
"rulesMatchType": "Tipo di Corrispondenza", "rulesMatchType": "Tipo di Corrispondenza",
"value": "Valore", "value": "Valore",
@@ -803,7 +792,7 @@
"rulesResource": "Configurazione Regole Risorsa", "rulesResource": "Configurazione Regole Risorsa",
"rulesResourceDescription": "Configura le regole per controllare l'accesso alla risorsa", "rulesResourceDescription": "Configura le regole per controllare l'accesso alla risorsa",
"ruleSubmit": "Aggiungi Regola", "ruleSubmit": "Aggiungi Regola",
"rulesNoOne": "No rules yet.", "rulesNoOne": "Nessuna regola. Aggiungi una regola usando il modulo.",
"rulesOrder": "Le regole sono valutate per priorità in ordine crescente.", "rulesOrder": "Le regole sono valutate per priorità in ordine crescente.",
"rulesSubmit": "Salva Regole", "rulesSubmit": "Salva Regole",
"policyErrorCreate": "Errore nella creazione della politica", "policyErrorCreate": "Errore nella creazione della politica",
@@ -814,44 +803,7 @@
"policyErrorUpdateMessageDescription": "Si è verificato un errore imprevisto", "policyErrorUpdateMessageDescription": "Si è verificato un errore imprevisto",
"policyCreatedSuccess": "Politica risorse creata con successo", "policyCreatedSuccess": "Politica risorse creata con successo",
"policyUpdatedSuccess": "Politica risorse aggiornata con successo", "policyUpdatedSuccess": "Politica risorse aggiornata con successo",
"authMethodsSave": "Save Settings", "authMethodsSave": "Salva metodi di autenticazione",
"policyAuthStackTitle": "Authentication",
"policyAuthStackDescription": "Control which authentication methods are required to access this resource",
"policyAuthOrLogicTitle": "Multiple authentication methods active",
"policyAuthOrLogicBanner": "Visitors may authenticate using any one of the active methods below. They do not need to complete all of them.",
"policyAuthMethodActive": "Active",
"policyAuthMethodOff": "Off",
"policyAuthSsoTitle": "Platform SSO",
"policyAuthSsoDescription": "Require sign-in through your organization's identity provider",
"policyAuthSsoSummary": "{idp} · {users} users, {roles} roles",
"policyAuthSsoDefaultIdp": "Default provider",
"policyAuthAddDefaultIdentityProvider": "Add Default Identity Provider",
"policyAuthOtherMethodsTitle": "Other Methods",
"policyAuthOtherMethodsDescription": "Optional methods visitors can use instead of or alongside platform SSO",
"policyAuthPasscodeTitle": "Passcode",
"policyAuthPasscodeDescription": "Require a shared alphanumeric passcode to access the resource",
"policyAuthPasscodeSummary": "Passcode set",
"policyAuthPincodeTitle": "PIN Code",
"policyAuthPincodeDescription": "A short numeric code required to access the resource",
"policyAuthPincodeSummary": "6-digit PIN set",
"policyAuthEmailTitle": "Email Whitelist",
"policyAuthEmailDescription": "Allow listed email addresses with one-time passwords",
"policyAuthEmailSummary": "{count} addresses allowed",
"policyAuthEmailOtpCallout": "Enabling email whitelist sends a one-time password to the visitor's email on login.",
"policyAuthHeaderAuthTitle": "Basic Header Auth",
"policyAuthHeaderAuthDescription": "Validate a custom HTTP header name and value on each request",
"policyAuthHeaderAuthSummary": "Header configured",
"policyAuthHeaderName": "Header name",
"policyAuthHeaderValue": "Expected value",
"policyAccessRulesTitle": "Access Rules",
"policyAccessRulesEnableDescription": "When enabled, rules are evaluated in descending order until one evaluates as true.",
"policyAccessRulesFirstMatch": "Rules are evaluated top to bottom. The first matching rule decides the outcome.",
"policyAccessRulesHowItWorks": "Rules match requests by path, IP address, location, or other criteria. Each rule applies an action: bypass authentication, block access, or pass to authentication. If no rule matches, traffic continues to authentication.",
"policyAccessRulesFallthroughOff": "When rules are disabled, all traffic passes through to authentication.",
"policyAccessRulesFallthroughOn": "When no rule matches, traffic passes through to authentication.",
"rulesPlaceholderCidr": "10.0.0.0/8",
"rulesPlaceholderPath": "/admin/*",
"rulesPlaceholderGeo": "RU, KP",
"rulesSave": "Salva Regole", "rulesSave": "Salva Regole",
"resourceErrorCreate": "Errore nella creazione della risorsa", "resourceErrorCreate": "Errore nella creazione della risorsa",
"resourceErrorCreateDescription": "Si è verificato un errore durante la creazione della risorsa", "resourceErrorCreateDescription": "Si è verificato un errore durante la creazione della risorsa",
@@ -1268,10 +1220,8 @@
"addLabels": "Aggiungi etichette", "addLabels": "Aggiungi etichette",
"siteLabelsTab": "Etichette", "siteLabelsTab": "Etichette",
"siteLabelsDescription": "Gestisci le etichette associate a questo sito.", "siteLabelsDescription": "Gestisci le etichette associate a questo sito.",
"labelsNotFound": "No labels found.", "labelsNotFound": "Etichette non trovate",
"labelsEmptyCreateHint": "Start typing above to create a label.",
"labelSearch": "Cerca etichette", "labelSearch": "Cerca etichette",
"labelSearchOrCreate": "Search or create a label",
"accessLabelFilterCount": "{count, plural, one {# etichetta} other {# etichette}}", "accessLabelFilterCount": "{count, plural, one {# etichetta} other {# etichette}}",
"labelOverflowCount": "+{count, plural, one {# etichetta} other {# etichette}}", "labelOverflowCount": "+{count, plural, one {# etichetta} other {# etichette}}",
"accessLabelFilterClear": "Cancella filtri etichette", "accessLabelFilterClear": "Cancella filtri etichette",
@@ -1512,7 +1462,7 @@
"sidebarProxyResources": "Pubblico", "sidebarProxyResources": "Pubblico",
"sidebarClientResources": "Privato", "sidebarClientResources": "Privato",
"sidebarPolicies": "Politiche", "sidebarPolicies": "Politiche",
"sidebarResourcePolicies": "Public Resources", "sidebarResourcePolicies": "Risorse",
"sidebarAccessControl": "Controllo Accesso", "sidebarAccessControl": "Controllo Accesso",
"sidebarLogsAndAnalytics": "Registri E Analisi", "sidebarLogsAndAnalytics": "Registri E Analisi",
"sidebarTeam": "Squadra", "sidebarTeam": "Squadra",
@@ -1520,7 +1470,7 @@
"sidebarAdmin": "Amministratore", "sidebarAdmin": "Amministratore",
"sidebarInvitations": "Inviti", "sidebarInvitations": "Inviti",
"sidebarRoles": "Ruoli", "sidebarRoles": "Ruoli",
"sidebarShareableLinks": "Share Links", "sidebarShareableLinks": "Collegamenti",
"sidebarApiKeys": "Chiavi API", "sidebarApiKeys": "Chiavi API",
"sidebarProvisioning": "Accantonamento", "sidebarProvisioning": "Accantonamento",
"sidebarSettings": "Impostazioni", "sidebarSettings": "Impostazioni",
@@ -1697,7 +1647,7 @@
"standaloneHcFilterResourceIdFallback": "Risorsa {id}", "standaloneHcFilterResourceIdFallback": "Risorsa {id}",
"blueprints": "Progetti", "blueprints": "Progetti",
"blueprintsLog": "Registro Progetti", "blueprintsLog": "Registro Progetti",
"blueprintsDescription": "View past blueprint applications and their results or apply a new blueprint", "blueprintsDescription": "Visualizza le applicazioni passate dei progetti e i loro risultati",
"blueprintAdd": "Aggiungi Progetto", "blueprintAdd": "Aggiungi Progetto",
"blueprintGoBack": "Vedi tutti i progetti", "blueprintGoBack": "Vedi tutti i progetti",
"blueprintCreate": "Crea Progetto", "blueprintCreate": "Crea Progetto",
@@ -2077,13 +2027,13 @@
"healthCheckUnknown": "Sconosciuto", "healthCheckUnknown": "Sconosciuto",
"healthCheck": "Controllo Salute", "healthCheck": "Controllo Salute",
"configureHealthCheck": "Configura Controllo Salute", "configureHealthCheck": "Configura Controllo Salute",
"configureHealthCheckDescription": "Set up monitoring for your resource to ensure it is always available", "configureHealthCheckDescription": "Imposta il monitoraggio della salute per {target}",
"enableHealthChecks": "Abilita i Controlli di Salute", "enableHealthChecks": "Abilita i Controlli di Salute",
"healthCheckDisabledStateDescription": "Quando disabilitato, il sito non eseguirà controlli di integrità e lo stato sarà considerato sconosciuto.", "healthCheckDisabledStateDescription": "Quando disabilitato, il sito non eseguirà controlli di integrità e lo stato sarà considerato sconosciuto.",
"enableHealthChecksDescription": "Monitorare lo stato di salute di questo obiettivo. Se necessario, è possibile monitorare un endpoint diverso da quello del bersaglio.", "enableHealthChecksDescription": "Monitorare lo stato di salute di questo obiettivo. Se necessario, è possibile monitorare un endpoint diverso da quello del bersaglio.",
"healthScheme": "Metodo", "healthScheme": "Metodo",
"healthSelectScheme": "Seleziona Metodo", "healthSelectScheme": "Seleziona Metodo",
"healthCheckPortInvalid": "Port must be between 1 and 65535", "healthCheckPortInvalid": "La porta di controllo dello stato di salute deve essere compresa tra 1 e 65535",
"healthCheckPath": "Percorso", "healthCheckPath": "Percorso",
"healthHostname": "IP / Nome host", "healthHostname": "IP / Nome host",
"healthPort": "Porta", "healthPort": "Porta",
@@ -2096,7 +2046,6 @@
"requireDeviceApproval": "Richiede Approvazioni Dispositivo", "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.", "requireDeviceApprovalDescription": "Gli utenti con questo ruolo hanno bisogno di nuovi dispositivi approvati da un amministratore prima di poter connettersi e accedere alle risorse.",
"sshSettings": "Impostazioni SSH", "sshSettings": "Impostazioni SSH",
"sshAccess": "SSH Access",
"rdpSettings": "Impostazioni RDP", "rdpSettings": "Impostazioni RDP",
"vncSettings": "Impostazioni VNC", "vncSettings": "Impostazioni VNC",
"sshServer": "Server SSH", "sshServer": "Server SSH",
@@ -2123,13 +2072,8 @@
"sshDaemonDisclaimer": "Assicurati che l'host target sia correttamente configurato per eseguire il demone di autenticazione prima di completare questa configurazione, altrimenti il provisioning fallirà.", "sshDaemonDisclaimer": "Assicurati che l'host target sia correttamente configurato per eseguire il demone di autenticazione prima di completare questa configurazione, altrimenti il provisioning fallirà.",
"sshDaemonPort": "Porta Daemon", "sshDaemonPort": "Porta Daemon",
"sshServerDestination": "Destinazione Server", "sshServerDestination": "Destinazione Server",
"sshServerDestinationDescription": "Configure the destination of the SSH server", "sshServerDestinationDescription": "Configura la destinazione e la porta del server SSH",
"destination": "Destinazione", "destination": "Destinazione",
"destinationRequired": "Destination is required.",
"domainRequired": "Domain is required.",
"proxyPortRequired": "Port is required.",
"invalidPathConfiguration": "Invalid path configuration.",
"invalidRewritePathConfiguration": "Invalid rewrite path configuration.",
"bgTargetMultiSiteDisclaimer": "Selezionare più siti abilita instradamento resiliente e failover per alta disponibilità.", "bgTargetMultiSiteDisclaimer": "Selezionare più siti abilita instradamento resiliente e failover per alta disponibilità.",
"roleAllowSsh": "Consenti SSH", "roleAllowSsh": "Consenti SSH",
"roleAllowSshAllow": "Consenti", "roleAllowSshAllow": "Consenti",
@@ -3090,7 +3034,7 @@
"enterConfirmation": "Inserisci conferma", "enterConfirmation": "Inserisci conferma",
"blueprintViewDetails": "Dettagli", "blueprintViewDetails": "Dettagli",
"defaultIdentityProvider": "Provider di Identità Predefinito", "defaultIdentityProvider": "Provider di Identità Predefinito",
"defaultIdentityProviderDescription": "The user will be automatically redirected to this identity provider for authentication.", "defaultIdentityProviderDescription": "Quando viene selezionato un provider di identità predefinito, l'utente verrà automaticamente reindirizzato al provider per l'autenticazione.",
"editInternalResourceDialogNetworkSettings": "Impostazioni di Rete", "editInternalResourceDialogNetworkSettings": "Impostazioni di Rete",
"editInternalResourceDialogAccessPolicy": "Politica di Accesso", "editInternalResourceDialogAccessPolicy": "Politica di Accesso",
"editInternalResourceDialogAddRoles": "Aggiungi Ruoli", "editInternalResourceDialogAddRoles": "Aggiungi Ruoli",
@@ -3402,8 +3346,6 @@
"idpUnassociateQuestion": "Sei sicuro di voler disassociare questo provider di identità da questa organizzazione?", "idpUnassociateQuestion": "Sei sicuro di voler disassociare questo provider di identità da questa organizzazione?",
"idpUnassociateDescription": "Tutti gli utenti associati a questo provider di identità verranno rimossi da questa organizzazione, ma il provider di identità continuerà ad esistere per altre organizzazioni associate.", "idpUnassociateDescription": "Tutti gli utenti associati a questo provider di identità verranno rimossi da questa organizzazione, ma il provider di identità continuerà ad esistere per altre organizzazioni associate.",
"idpUnassociateConfirm": "Conferma Disassociazione Provider di Identità", "idpUnassociateConfirm": "Conferma Disassociazione Provider di Identità",
"idpConfirmDeleteAndRemoveMeFromOrg": "DELETE AND REMOVE ME FROM ORG",
"idpUnassociateAndRemoveMeFromOrg": "UNASSOCIATE AND REMOVE ME FROM ORG",
"idpUnassociateWarning": "Questo non può essere annullato per questa organizzazione.", "idpUnassociateWarning": "Questo non può essere annullato per questa organizzazione.",
"idpUnassociatedDescription": "Provider di identità disassociato con successo da questa organizzazione", "idpUnassociatedDescription": "Provider di identità disassociato con successo da questa organizzazione",
"idpUnassociateMenu": "Disassocia", "idpUnassociateMenu": "Disassocia",
@@ -3510,43 +3452,5 @@
"sshErrorNoTarget": "Nessun obiettivo specificato", "sshErrorNoTarget": "Nessun obiettivo specificato",
"sshErrorWebSocket": "Connessione WebSocket fallita", "sshErrorWebSocket": "Connessione WebSocket fallita",
"sshErrorAuthFailed": "Autenticazione fallita", "sshErrorAuthFailed": "Autenticazione fallita",
"sshErrorConnectionClosed": "Connessione chiusa prima del completamento dell'autenticazione", "sshErrorConnectionClosed": "Connessione chiusa prima del completamento dell'autenticazione"
"sitePangolinSshDescription": "Allow SSH access to resources on this site. This can be changed later.",
"browserGatewayNoResourceForDomain": "No resource found for this domain",
"browserGatewayNoTarget": "No target",
"browserGatewayConnect": "Connect",
"browserGatewayCtrlAltDel": "Ctrl+Alt+Del",
"sshErrorSignKeyFailed": "Failed to sign SSH key for PAM push authentication. Did you sign in as a user?",
"sshTerminalError": "Error: {error}",
"sshConnectionClosedCode": "Connection closed (code {code})",
"sshPrivateKeyPlaceholder": "-----BEGIN OPENSSH PRIVATE KEY-----",
"sshPrivateKeyRequired": "Private key is required",
"vncTitle": "VNC",
"vncSignInDescription": "Enter your VNC password to connect",
"vncPasswordOptional": "Password (optional)",
"vncNoResourceTarget": "No resource target is available",
"vncFailedToLoadNovnc": "Failed to load noVNC",
"vncAuthFailedStatus": "Status {status}",
"vncPasteClipboard": "Paste clipboard",
"rdpTitle": "RDP",
"rdpSignInTitle": "Sign in to Remote Desktop",
"rdpSignInDescription": "Enter Windows credentials to connect",
"rdpLoadingModule": "Loading module...",
"rdpFailedToLoadModule": "Failed to load RDP module",
"rdpNotReady": "Not ready",
"rdpModuleInitializing": "RDP module is still initializing",
"rdpDownloadingFiles": "Downloading {count} file(s) from remote…",
"rdpDownloadFailed": "Download failed: {fileName}",
"rdpUploaded": "Uploaded: {fileName}",
"rdpNoConnectionTarget": "No connection target available",
"rdpConnectionFailed": "Connection failed",
"rdpFit": "Fit",
"rdpFull": "Full",
"rdpReal": "Real",
"rdpMeta": "Meta",
"rdpUploadFiles": "Upload files",
"rdpFilesReadyToPaste": "Files ready to paste",
"rdpFilesReadyToPasteDescription": "{count} file(s) copied to remote clipboard — press Ctrl+V on the remote desktop to paste.",
"rdpUploadFailed": "Upload failed",
"rdpUnicodeKeyboardMode": "Unicode keyboard mode"
} }
+27 -123
View File
@@ -101,8 +101,6 @@
"sitesTableViewPrivateResources": "개인 리소스 보기", "sitesTableViewPrivateResources": "개인 리소스 보기",
"siteInstallNewt": "Newt 설치", "siteInstallNewt": "Newt 설치",
"siteInstallNewtDescription": "시스템에서 Newt 실행하기", "siteInstallNewtDescription": "시스템에서 Newt 실행하기",
"siteInstallKubernetesDocsDescription": "For more and up to date Kubernetes installation information, see <docsLink>docs.pangolin.net/manage/sites/install-kubernetes</docsLink>.",
"siteInstallAdvantechDocsDescription": "For Advantech modem installation instructions, see <docsLink>docs.pangolin.net/manage/sites/install-advantech</docsLink>.",
"WgConfiguration": "WireGuard 구성", "WgConfiguration": "WireGuard 구성",
"WgConfigurationDescription": "네트워크에 연결하기 위한 다음 구성을 사용하세요.", "WgConfigurationDescription": "네트워크에 연결하기 위한 다음 구성을 사용하세요.",
"operatingSystem": "운영 체제", "operatingSystem": "운영 체제",
@@ -202,8 +200,8 @@
"shareErrorSelectResource": "리소스를 선택하세요", "shareErrorSelectResource": "리소스를 선택하세요",
"proxyResourceTitle": "공개 리소스 관리", "proxyResourceTitle": "공개 리소스 관리",
"proxyResourceDescription": "웹 브라우저를 통해 공용으로 접근할 수 있는 리소스를 생성하고 관리하세요.", "proxyResourceDescription": "웹 브라우저를 통해 공용으로 접근할 수 있는 리소스를 생성하고 관리하세요.",
"publicResourcesBannerTitle": "Web-based Public Access", "publicResourcesBannerTitle": "웹 기반 공공 접근",
"publicResourcesBannerDescription": "Public resources are HTTPS proxies accessible to anyone on the internet through a web browser. Unlike private resources, they do not require client-side software and can include identity and context-aware access policies.", "publicResourcesBannerDescription": "공공 자원은 누구나 웹 브라우저를 통해 접근 가능한 HTTPS 또는 TCP/UDP 프록시입니다. 개인 자원과 달리 클라이언트 측 소프트웨어가 필요하지 않으며, 아이덴티티 및 컨텍스트 인지 접근 정책을 포함할 수 있습니다.",
"clientResourceTitle": "개인 리소스 관리", "clientResourceTitle": "개인 리소스 관리",
"clientResourceDescription": "연결된 클라이언트를 통해서만 접근할 수 있는 리소스를 생성하고 관리하세요.", "clientResourceDescription": "연결된 클라이언트를 통해서만 접근할 수 있는 리소스를 생성하고 관리하세요.",
"privateResourcesBannerTitle": "제로 트러스트 개인 접근", "privateResourcesBannerTitle": "제로 트러스트 개인 접근",
@@ -211,16 +209,15 @@
"resourcesSearch": "리소스 검색...", "resourcesSearch": "리소스 검색...",
"resourceAdd": "리소스 추가", "resourceAdd": "리소스 추가",
"resourceErrorDelte": "리소스 삭제 중 오류 발생", "resourceErrorDelte": "리소스 삭제 중 오류 발생",
"resourcePoliciesTitle": "Manage Public Resource Policies", "resourcePoliciesTitle": "리소스 정책 관리",
"resourcePoliciesAttachedResourcesColumnTitle": "Resources", "resourcePoliciesAttachedResourcesColumnTitle": "첨부 리소스",
"resourcePoliciesAttachedResources": "{count} 리소스", "resourcePoliciesAttachedResources": "{count} 리소스",
"resourcePoliciesAttachedResourcesCount": "{count, plural, one {# resource} other {# resources}}",
"resourcePoliciesAttachedResourcesEmpty": "리소스 없음", "resourcePoliciesAttachedResourcesEmpty": "리소스 없음",
"resourcePoliciesDescription": "Create and manage authentication policies to control access to your public resources", "resourcePoliciesDescription": "리소스에 대한 접근을 제어할 인증 정책을 생성 및 관리합니다",
"resourcePoliciesSearch": "정책 검색...", "resourcePoliciesSearch": "정책 검색...",
"resourcePoliciesAdd": "정책 추가", "resourcePoliciesAdd": "정책 추가",
"resourcePoliciesDefaultBadgeText": "기본 정책", "resourcePoliciesDefaultBadgeText": "기본 정책",
"resourcePoliciesCreate": "Create Public Resource Policy", "resourcePoliciesCreate": "리소스 정책 생성",
"resourcePoliciesCreateDescription": "새로운 정책을 생성하려면 아래 단계들을 따르세요", "resourcePoliciesCreateDescription": "새로운 정책을 생성하려면 아래 단계들을 따르세요",
"resourcePolicyName": "정책 이름", "resourcePolicyName": "정책 이름",
"resourcePolicyNameDescription": "이 정책에 리소스 간에 식별할 이름을 지정합니다", "resourcePolicyNameDescription": "이 정책에 리소스 간에 식별할 이름을 지정합니다",
@@ -314,7 +311,7 @@
"rules": "규칙", "rules": "규칙",
"resourceSettingDescription": "리소스의 설정을 구성하세요.", "resourceSettingDescription": "리소스의 설정을 구성하세요.",
"resourceSetting": "{resourceName} 설정", "resourceSetting": "{resourceName} 설정",
"resourcePolicySettingDescription": "Configure the settings on this public resource policy", "resourcePolicySettingDescription": "리소스 정책에 대한 설정을 구성합니다",
"resourcePolicySetting": "{policyName} 설정", "resourcePolicySetting": "{policyName} 설정",
"alwaysAllow": "인증 우회", "alwaysAllow": "인증 우회",
"alwaysDeny": "접근 차단", "alwaysDeny": "접근 차단",
@@ -756,11 +753,11 @@
"rulesErrorDuplicate": "중복 규칙", "rulesErrorDuplicate": "중복 규칙",
"rulesErrorDuplicateDescription": "이 설정을 가진 규칙이 이미 존재합니다.", "rulesErrorDuplicateDescription": "이 설정을 가진 규칙이 이미 존재합니다.",
"rulesErrorInvalidIpAddressRange": "유효하지 않은 CIDR", "rulesErrorInvalidIpAddressRange": "유효하지 않은 CIDR",
"rulesErrorInvalidIpAddressRangeDescription": "Enter a valid CIDR range (e.g., 10.0.0.0/8).", "rulesErrorInvalidIpAddressRangeDescription": "유효한 CIDR 값을 입력하십시오.",
"rulesErrorInvalidUrl": "Invalid path", "rulesErrorInvalidUrl": "유효하지 않은 URL 경로",
"rulesErrorInvalidUrlDescription": "Enter a valid URL path or pattern (e.g., /api/*).", "rulesErrorInvalidUrlDescription": "유효한 URL 경로 값을 입력해 주세요.",
"rulesErrorInvalidIpAddress": "Invalid IP address", "rulesErrorInvalidIpAddress": "유효하지 않은 IP",
"rulesErrorInvalidIpAddressDescription": "Enter a valid IPv4 or IPv6 address.", "rulesErrorInvalidIpAddressDescription": "유효한 IP 주소를 입력하세요",
"rulesErrorUpdate": "규칙 업데이트에 실패했습니다.", "rulesErrorUpdate": "규칙 업데이트에 실패했습니다.",
"rulesErrorUpdateDescription": "규칙 업데이트 중 오류가 발생했습니다.", "rulesErrorUpdateDescription": "규칙 업데이트 중 오류가 발생했습니다.",
"rulesUpdated": "규칙 활성화", "rulesUpdated": "규칙 활성화",
@@ -768,23 +765,15 @@
"rulesMatchIpAddressRangeDescription": "CIDR 형식으로 주소를 입력하세요 (예: 103.21.244.0/22)", "rulesMatchIpAddressRangeDescription": "CIDR 형식으로 주소를 입력하세요 (예: 103.21.244.0/22)",
"rulesMatchIpAddress": "IP 주소를 입력하세요 (예: 103.21.244.12)", "rulesMatchIpAddress": "IP 주소를 입력하세요 (예: 103.21.244.12)",
"rulesMatchUrl": "URL 경로 또는 패턴을 입력하세요 (예: /api/v1/todos 또는 /api/v1/*)", "rulesMatchUrl": "URL 경로 또는 패턴을 입력하세요 (예: /api/v1/todos 또는 /api/v1/*)",
"rulesErrorInvalidPriority": "Invalid priority", "rulesErrorInvalidPriority": "유효하지 않은 우선순위",
"rulesErrorInvalidPriorityDescription": "Enter a whole number of 1 or higher.", "rulesErrorInvalidPriorityDescription": "유효한 우선 순위를 입력하세요.",
"rulesErrorDuplicatePriority": "Duplicate priorities", "rulesErrorDuplicatePriority": "중복 우선순위",
"rulesErrorDuplicatePriorityDescription": "Each rule must have a unique priority number.", "rulesErrorDuplicatePriorityDescription": "고유한 우선 순위를 입력하십시오.",
"rulesErrorValidation": "Invalid rules",
"rulesErrorValidationRuleDescription": "Rule {ruleNumber}: {message}",
"rulesErrorValueRequired": "Enter a value for this rule.",
"rulesErrorInvalidCountry": "Invalid country",
"rulesErrorInvalidCountryDescription": "Select a valid country.",
"rulesErrorInvalidAsn": "Invalid ASN",
"rulesErrorInvalidAsnDescription": "Enter a valid ASN (e.g., AS15169).",
"ruleUpdated": "규칙이 업데이트되었습니다", "ruleUpdated": "규칙이 업데이트되었습니다",
"ruleUpdatedDescription": "규칙이 성공적으로 업데이트되었습니다", "ruleUpdatedDescription": "규칙이 성공적으로 업데이트되었습니다",
"ruleErrorUpdate": "작업 실패", "ruleErrorUpdate": "작업 실패",
"ruleErrorUpdateDescription": "저장 작업 중 오류가 발생했습니다.", "ruleErrorUpdateDescription": "저장 작업 중 오류가 발생했습니다.",
"rulesPriority": "우선순위", "rulesPriority": "우선순위",
"rulesReorderDragHandle": "Drag to reorder rule priority",
"rulesAction": "작업", "rulesAction": "작업",
"rulesMatchType": "일치 유형", "rulesMatchType": "일치 유형",
"value": "값", "value": "값",
@@ -803,7 +792,7 @@
"rulesResource": "리소스 규칙 구성", "rulesResource": "리소스 규칙 구성",
"rulesResourceDescription": "리소스에 대한 접근을 제어하는 규칙 구성", "rulesResourceDescription": "리소스에 대한 접근을 제어하는 규칙 구성",
"ruleSubmit": "규칙 추가", "ruleSubmit": "규칙 추가",
"rulesNoOne": "No rules yet.", "rulesNoOne": "규칙이 없습니다. 양식을 사용하여 규칙을 추가하십시오.",
"rulesOrder": "규칙은 우선 순위에 따라 오름차순으로 평가됩니다.", "rulesOrder": "규칙은 우선 순위에 따라 오름차순으로 평가됩니다.",
"rulesSubmit": "규칙 저장", "rulesSubmit": "규칙 저장",
"policyErrorCreate": "정책 생성 오류", "policyErrorCreate": "정책 생성 오류",
@@ -814,44 +803,7 @@
"policyErrorUpdateMessageDescription": "예기치 않은 오류가 발생했습니다", "policyErrorUpdateMessageDescription": "예기치 않은 오류가 발생했습니다",
"policyCreatedSuccess": "리소스 정책이 성공적으로 생성되었습니다", "policyCreatedSuccess": "리소스 정책이 성공적으로 생성되었습니다",
"policyUpdatedSuccess": "리소스 정책이 성공적으로 업데이트되었습니다", "policyUpdatedSuccess": "리소스 정책이 성공적으로 업데이트되었습니다",
"authMethodsSave": "Save Settings", "authMethodsSave": "인증 방법 저장",
"policyAuthStackTitle": "Authentication",
"policyAuthStackDescription": "Control which authentication methods are required to access this resource",
"policyAuthOrLogicTitle": "Multiple authentication methods active",
"policyAuthOrLogicBanner": "Visitors may authenticate using any one of the active methods below. They do not need to complete all of them.",
"policyAuthMethodActive": "Active",
"policyAuthMethodOff": "Off",
"policyAuthSsoTitle": "Platform SSO",
"policyAuthSsoDescription": "Require sign-in through your organization's identity provider",
"policyAuthSsoSummary": "{idp} · {users} users, {roles} roles",
"policyAuthSsoDefaultIdp": "Default provider",
"policyAuthAddDefaultIdentityProvider": "Add Default Identity Provider",
"policyAuthOtherMethodsTitle": "Other Methods",
"policyAuthOtherMethodsDescription": "Optional methods visitors can use instead of or alongside platform SSO",
"policyAuthPasscodeTitle": "Passcode",
"policyAuthPasscodeDescription": "Require a shared alphanumeric passcode to access the resource",
"policyAuthPasscodeSummary": "Passcode set",
"policyAuthPincodeTitle": "PIN Code",
"policyAuthPincodeDescription": "A short numeric code required to access the resource",
"policyAuthPincodeSummary": "6-digit PIN set",
"policyAuthEmailTitle": "Email Whitelist",
"policyAuthEmailDescription": "Allow listed email addresses with one-time passwords",
"policyAuthEmailSummary": "{count} addresses allowed",
"policyAuthEmailOtpCallout": "Enabling email whitelist sends a one-time password to the visitor's email on login.",
"policyAuthHeaderAuthTitle": "Basic Header Auth",
"policyAuthHeaderAuthDescription": "Validate a custom HTTP header name and value on each request",
"policyAuthHeaderAuthSummary": "Header configured",
"policyAuthHeaderName": "Header name",
"policyAuthHeaderValue": "Expected value",
"policyAccessRulesTitle": "Access Rules",
"policyAccessRulesEnableDescription": "When enabled, rules are evaluated in descending order until one evaluates as true.",
"policyAccessRulesFirstMatch": "Rules are evaluated top to bottom. The first matching rule decides the outcome.",
"policyAccessRulesHowItWorks": "Rules match requests by path, IP address, location, or other criteria. Each rule applies an action: bypass authentication, block access, or pass to authentication. If no rule matches, traffic continues to authentication.",
"policyAccessRulesFallthroughOff": "When rules are disabled, all traffic passes through to authentication.",
"policyAccessRulesFallthroughOn": "When no rule matches, traffic passes through to authentication.",
"rulesPlaceholderCidr": "10.0.0.0/8",
"rulesPlaceholderPath": "/admin/*",
"rulesPlaceholderGeo": "RU, KP",
"rulesSave": "규칙 저장", "rulesSave": "규칙 저장",
"resourceErrorCreate": "리소스 생성 오류", "resourceErrorCreate": "리소스 생성 오류",
"resourceErrorCreateDescription": "리소스를 생성하는 중 오류가 발생했습니다.", "resourceErrorCreateDescription": "리소스를 생성하는 중 오류가 발생했습니다.",
@@ -1268,10 +1220,8 @@
"addLabels": "레이블 추가", "addLabels": "레이블 추가",
"siteLabelsTab": "레이블", "siteLabelsTab": "레이블",
"siteLabelsDescription": "이 사이트와 연결된 레이블을 관리합니다.", "siteLabelsDescription": "이 사이트와 연결된 레이블을 관리합니다.",
"labelsNotFound": "No labels found.", "labelsNotFound": "레이블을 찾을 수 없습니다",
"labelsEmptyCreateHint": "Start typing above to create a label.",
"labelSearch": "레이블 검색", "labelSearch": "레이블 검색",
"labelSearchOrCreate": "Search or create a label",
"accessLabelFilterCount": "{count, plural, other {# 레이블}}", "accessLabelFilterCount": "{count, plural, other {# 레이블}}",
"labelOverflowCount": " +{count, plural, other {# 레이블}}", "labelOverflowCount": " +{count, plural, other {# 레이블}}",
"accessLabelFilterClear": "레이블 필터 초기화", "accessLabelFilterClear": "레이블 필터 초기화",
@@ -1512,7 +1462,7 @@
"sidebarProxyResources": "공유", "sidebarProxyResources": "공유",
"sidebarClientResources": "비공개", "sidebarClientResources": "비공개",
"sidebarPolicies": "정책", "sidebarPolicies": "정책",
"sidebarResourcePolicies": "Public Resources", "sidebarResourcePolicies": "리소스",
"sidebarAccessControl": "액세스 제어", "sidebarAccessControl": "액세스 제어",
"sidebarLogsAndAnalytics": "로그 및 분석", "sidebarLogsAndAnalytics": "로그 및 분석",
"sidebarTeam": "팀", "sidebarTeam": "팀",
@@ -1520,7 +1470,7 @@
"sidebarAdmin": "관리자", "sidebarAdmin": "관리자",
"sidebarInvitations": "초대", "sidebarInvitations": "초대",
"sidebarRoles": "역할", "sidebarRoles": "역할",
"sidebarShareableLinks": "Share Links", "sidebarShareableLinks": "링크",
"sidebarApiKeys": "API 키", "sidebarApiKeys": "API 키",
"sidebarProvisioning": "프로비저닝", "sidebarProvisioning": "프로비저닝",
"sidebarSettings": "설정", "sidebarSettings": "설정",
@@ -1697,7 +1647,7 @@
"standaloneHcFilterResourceIdFallback": "리소스 {id}", "standaloneHcFilterResourceIdFallback": "리소스 {id}",
"blueprints": "청사진", "blueprints": "청사진",
"blueprintsLog": "블루프린트 로그", "blueprintsLog": "블루프린트 로그",
"blueprintsDescription": "View past blueprint applications and their results or apply a new blueprint", "blueprintsDescription": "과거 블루프린트 적용 및 결과 보기",
"blueprintAdd": "청사진 추가", "blueprintAdd": "청사진 추가",
"blueprintGoBack": "모든 청사진 보기", "blueprintGoBack": "모든 청사진 보기",
"blueprintCreate": "청사진 생성", "blueprintCreate": "청사진 생성",
@@ -2077,13 +2027,13 @@
"healthCheckUnknown": "알 수 없음", "healthCheckUnknown": "알 수 없음",
"healthCheck": "상태 확인", "healthCheck": "상태 확인",
"configureHealthCheck": "상태 확인 설정", "configureHealthCheck": "상태 확인 설정",
"configureHealthCheckDescription": "Set up monitoring for your resource to ensure it is always available", "configureHealthCheckDescription": "{target}에 대한 상태 모니터링 설정",
"enableHealthChecks": "상태 확인 활성화", "enableHealthChecks": "상태 확인 활성화",
"healthCheckDisabledStateDescription": "비활성화되면 이 사이트가 상태 확인을 수행하지 않으며 상태가 알 수 없는 것으로 간주됩니다.", "healthCheckDisabledStateDescription": "비활성화되면 이 사이트가 상태 확인을 수행하지 않으며 상태가 알 수 없는 것으로 간주됩니다.",
"enableHealthChecksDescription": "이 대상을 모니터링하여 건강 상태를 확인하세요. 필요에 따라 대상과 다른 엔드포인트를 모니터링할 수 있습니다.", "enableHealthChecksDescription": "이 대상을 모니터링하여 건강 상태를 확인하세요. 필요에 따라 대상과 다른 엔드포인트를 모니터링할 수 있습니다.",
"healthScheme": "방법", "healthScheme": "방법",
"healthSelectScheme": "방법 선택", "healthSelectScheme": "방법 선택",
"healthCheckPortInvalid": "Port must be between 1 and 65535", "healthCheckPortInvalid": "올바르지 않은 서브넷 마스크입니다. 1에서 65535 사이여야 합니다",
"healthCheckPath": "경로", "healthCheckPath": "경로",
"healthHostname": "IP / 호스트", "healthHostname": "IP / 호스트",
"healthPort": "포트", "healthPort": "포트",
@@ -2096,7 +2046,6 @@
"requireDeviceApproval": "장치 승인 요구", "requireDeviceApproval": "장치 승인 요구",
"requireDeviceApprovalDescription": "이 역할을 가진 사용자는 장치가 연결되기 전에 관리자의 승인이 필요합니다.", "requireDeviceApprovalDescription": "이 역할을 가진 사용자는 장치가 연결되기 전에 관리자의 승인이 필요합니다.",
"sshSettings": "SSH 설정", "sshSettings": "SSH 설정",
"sshAccess": "SSH Access",
"rdpSettings": "RDP 설정", "rdpSettings": "RDP 설정",
"vncSettings": "VNC 설정", "vncSettings": "VNC 설정",
"sshServer": "SSH 서버", "sshServer": "SSH 서버",
@@ -2123,13 +2072,8 @@
"sshDaemonDisclaimer": "이 설정을 완료하기 전에 인증 데몬을 실행할 대상 호스트가 적절히 구성되었는지 확인하십시오. 그렇지 않으면 프로비저닝이 실패할 수 있습니다.", "sshDaemonDisclaimer": "이 설정을 완료하기 전에 인증 데몬을 실행할 대상 호스트가 적절히 구성되었는지 확인하십시오. 그렇지 않으면 프로비저닝이 실패할 수 있습니다.",
"sshDaemonPort": "데몬 포트", "sshDaemonPort": "데몬 포트",
"sshServerDestination": "서버 목적지", "sshServerDestination": "서버 목적지",
"sshServerDestinationDescription": "Configure the destination of the SSH server", "sshServerDestinationDescription": "SSH 서버의 목적지 및 포트를 구성합니다",
"destination": "대상지", "destination": "대상지",
"destinationRequired": "Destination is required.",
"domainRequired": "Domain is required.",
"proxyPortRequired": "Port is required.",
"invalidPathConfiguration": "Invalid path configuration.",
"invalidRewritePathConfiguration": "Invalid rewrite path configuration.",
"bgTargetMultiSiteDisclaimer": "여러 사이트를 선택하면 고가용성을 위한 내구성 있는 라우팅 및 장애 조치를 활성화합니다.", "bgTargetMultiSiteDisclaimer": "여러 사이트를 선택하면 고가용성을 위한 내구성 있는 라우팅 및 장애 조치를 활성화합니다.",
"roleAllowSsh": "SSH 허용", "roleAllowSsh": "SSH 허용",
"roleAllowSshAllow": "허용", "roleAllowSshAllow": "허용",
@@ -3090,7 +3034,7 @@
"enterConfirmation": "확인 입력", "enterConfirmation": "확인 입력",
"blueprintViewDetails": "세부 정보", "blueprintViewDetails": "세부 정보",
"defaultIdentityProvider": "기본 아이덴티티 공급자", "defaultIdentityProvider": "기본 아이덴티티 공급자",
"defaultIdentityProviderDescription": "The user will be automatically redirected to this identity provider for authentication.", "defaultIdentityProviderDescription": "기본 ID 공급자가 선택되면, 사용자는 인증을 위해 자동으로 해당 공급자로 리디렉션됩니다.",
"editInternalResourceDialogNetworkSettings": "네트워크 설정", "editInternalResourceDialogNetworkSettings": "네트워크 설정",
"editInternalResourceDialogAccessPolicy": "액세스 정책", "editInternalResourceDialogAccessPolicy": "액세스 정책",
"editInternalResourceDialogAddRoles": "역할 추가", "editInternalResourceDialogAddRoles": "역할 추가",
@@ -3402,8 +3346,6 @@
"idpUnassociateQuestion": "정말로 이 조직에서 이 아이덴티티 공급자의 연관을 해제하시겠습니까?", "idpUnassociateQuestion": "정말로 이 조직에서 이 아이덴티티 공급자의 연관을 해제하시겠습니까?",
"idpUnassociateDescription": "이 아이덴티티 공급자와 연관된 모든 사용자는 이 조직에서 제거될 것이지만, 아이덴티티 공급자는 다른 연관된 조직에 계속해서 존재할 것입니다.", "idpUnassociateDescription": "이 아이덴티티 공급자와 연관된 모든 사용자는 이 조직에서 제거될 것이지만, 아이덴티티 공급자는 다른 연관된 조직에 계속해서 존재할 것입니다.",
"idpUnassociateConfirm": "아이덴티티 공급자 연관 해제 확인", "idpUnassociateConfirm": "아이덴티티 공급자 연관 해제 확인",
"idpConfirmDeleteAndRemoveMeFromOrg": "DELETE AND REMOVE ME FROM ORG",
"idpUnassociateAndRemoveMeFromOrg": "UNASSOCIATE AND REMOVE ME FROM ORG",
"idpUnassociateWarning": "이 조직에서 이것은 되돌릴 수 없습니다.", "idpUnassociateWarning": "이 조직에서 이것은 되돌릴 수 없습니다.",
"idpUnassociatedDescription": "아이덴티티 공급자가 이 조직에서 성공적으로 연관 해제되었습니다", "idpUnassociatedDescription": "아이덴티티 공급자가 이 조직에서 성공적으로 연관 해제되었습니다",
"idpUnassociateMenu": "연관 해제", "idpUnassociateMenu": "연관 해제",
@@ -3510,43 +3452,5 @@
"sshErrorNoTarget": "지정된 대상이 없습니다", "sshErrorNoTarget": "지정된 대상이 없습니다",
"sshErrorWebSocket": "WebSocket 연결 실패", "sshErrorWebSocket": "WebSocket 연결 실패",
"sshErrorAuthFailed": "인증 실패", "sshErrorAuthFailed": "인증 실패",
"sshErrorConnectionClosed": "인증이 완료되기 전에 연결이 닫혔습니다", "sshErrorConnectionClosed": "인증이 완료되기 전에 연결이 닫혔습니다"
"sitePangolinSshDescription": "Allow SSH access to resources on this site. This can be changed later.",
"browserGatewayNoResourceForDomain": "No resource found for this domain",
"browserGatewayNoTarget": "No target",
"browserGatewayConnect": "Connect",
"browserGatewayCtrlAltDel": "Ctrl+Alt+Del",
"sshErrorSignKeyFailed": "Failed to sign SSH key for PAM push authentication. Did you sign in as a user?",
"sshTerminalError": "Error: {error}",
"sshConnectionClosedCode": "Connection closed (code {code})",
"sshPrivateKeyPlaceholder": "-----BEGIN OPENSSH PRIVATE KEY-----",
"sshPrivateKeyRequired": "Private key is required",
"vncTitle": "VNC",
"vncSignInDescription": "Enter your VNC password to connect",
"vncPasswordOptional": "Password (optional)",
"vncNoResourceTarget": "No resource target is available",
"vncFailedToLoadNovnc": "Failed to load noVNC",
"vncAuthFailedStatus": "Status {status}",
"vncPasteClipboard": "Paste clipboard",
"rdpTitle": "RDP",
"rdpSignInTitle": "Sign in to Remote Desktop",
"rdpSignInDescription": "Enter Windows credentials to connect",
"rdpLoadingModule": "Loading module...",
"rdpFailedToLoadModule": "Failed to load RDP module",
"rdpNotReady": "Not ready",
"rdpModuleInitializing": "RDP module is still initializing",
"rdpDownloadingFiles": "Downloading {count} file(s) from remote…",
"rdpDownloadFailed": "Download failed: {fileName}",
"rdpUploaded": "Uploaded: {fileName}",
"rdpNoConnectionTarget": "No connection target available",
"rdpConnectionFailed": "Connection failed",
"rdpFit": "Fit",
"rdpFull": "Full",
"rdpReal": "Real",
"rdpMeta": "Meta",
"rdpUploadFiles": "Upload files",
"rdpFilesReadyToPaste": "Files ready to paste",
"rdpFilesReadyToPasteDescription": "{count} file(s) copied to remote clipboard — press Ctrl+V on the remote desktop to paste.",
"rdpUploadFailed": "Upload failed",
"rdpUnicodeKeyboardMode": "Unicode keyboard mode"
} }
+27 -123
View File
@@ -101,8 +101,6 @@
"sitesTableViewPrivateResources": "Vis private ressurser", "sitesTableViewPrivateResources": "Vis private ressurser",
"siteInstallNewt": "Installer Newt", "siteInstallNewt": "Installer Newt",
"siteInstallNewtDescription": "Få Newt til å kjøre på systemet ditt", "siteInstallNewtDescription": "Få Newt til å kjøre på systemet ditt",
"siteInstallKubernetesDocsDescription": "For more and up to date Kubernetes installation information, see <docsLink>docs.pangolin.net/manage/sites/install-kubernetes</docsLink>.",
"siteInstallAdvantechDocsDescription": "For Advantech modem installation instructions, see <docsLink>docs.pangolin.net/manage/sites/install-advantech</docsLink>.",
"WgConfiguration": "WireGuard Konfigurasjon", "WgConfiguration": "WireGuard Konfigurasjon",
"WgConfigurationDescription": "Bruk følgende konfigurasjon for å koble til nettverket", "WgConfigurationDescription": "Bruk følgende konfigurasjon for å koble til nettverket",
"operatingSystem": "Operativsystem", "operatingSystem": "Operativsystem",
@@ -202,8 +200,8 @@
"shareErrorSelectResource": "Vennligst velg en ressurs", "shareErrorSelectResource": "Vennligst velg en ressurs",
"proxyResourceTitle": "Administrere offentlige ressurser", "proxyResourceTitle": "Administrere offentlige ressurser",
"proxyResourceDescription": "Opprett og administrer ressurser som er offentlig tilgjengelige via en nettleser", "proxyResourceDescription": "Opprett og administrer ressurser som er offentlig tilgjengelige via en nettleser",
"publicResourcesBannerTitle": "Web-based Public Access", "publicResourcesBannerTitle": "Nettbasert offentlig tilgang",
"publicResourcesBannerDescription": "Public resources are HTTPS proxies accessible to anyone on the internet through a web browser. Unlike private resources, they do not require client-side software and can include identity and context-aware access policies.", "publicResourcesBannerDescription": "Offentlige ressurser er HTTPS- eller TCP/UDP-proxyer tilgjengelige for alle på internett via en nettleser. I motsetning til private ressurser, krever de ikke klient-basert programvare og kan inkludere identitets- og kontekstbevisste tilgangspolicyer.",
"clientResourceTitle": "Administrer private ressurser", "clientResourceTitle": "Administrer private ressurser",
"clientResourceDescription": "Opprette og administrere ressurser som bare er tilgjengelige via en tilkoblet klient", "clientResourceDescription": "Opprette og administrere ressurser som bare er tilgjengelige via en tilkoblet klient",
"privateResourcesBannerTitle": "Zero-Trust privat tilgang", "privateResourcesBannerTitle": "Zero-Trust privat tilgang",
@@ -211,16 +209,15 @@
"resourcesSearch": "Søk i ressurser...", "resourcesSearch": "Søk i ressurser...",
"resourceAdd": "Legg til ressurs", "resourceAdd": "Legg til ressurs",
"resourceErrorDelte": "Feil ved sletting av ressurs", "resourceErrorDelte": "Feil ved sletting av ressurs",
"resourcePoliciesTitle": "Manage Public Resource Policies", "resourcePoliciesTitle": "Administrer Ressurspolitikk",
"resourcePoliciesAttachedResourcesColumnTitle": "Resources", "resourcePoliciesAttachedResourcesColumnTitle": "Vedlagte ressurser",
"resourcePoliciesAttachedResources": "{count} ressurs(er)", "resourcePoliciesAttachedResources": "{count} ressurs(er)",
"resourcePoliciesAttachedResourcesCount": "{count, plural, one {# resource} other {# resources}}",
"resourcePoliciesAttachedResourcesEmpty": "ingen ressurser", "resourcePoliciesAttachedResourcesEmpty": "ingen ressurser",
"resourcePoliciesDescription": "Create and manage authentication policies to control access to your public resources", "resourcePoliciesDescription": "Opprett og administrer autentiseringsregler for å kontrollere tilgang til dine ressurser",
"resourcePoliciesSearch": "Søk etter regler...", "resourcePoliciesSearch": "Søk etter regler...",
"resourcePoliciesAdd": "Legg til policy", "resourcePoliciesAdd": "Legg til policy",
"resourcePoliciesDefaultBadgeText": "Standard politisk", "resourcePoliciesDefaultBadgeText": "Standard politisk",
"resourcePoliciesCreate": "Create Public Resource Policy", "resourcePoliciesCreate": "Opprett Ressurspolitikk",
"resourcePoliciesCreateDescription": "Følg trinnene nedenfor for å lage en ny policy", "resourcePoliciesCreateDescription": "Følg trinnene nedenfor for å lage en ny policy",
"resourcePolicyName": "Polisnavn", "resourcePolicyName": "Polisnavn",
"resourcePolicyNameDescription": "Gi denne policynavnet for å identifisere den på tvers av dine ressurser", "resourcePolicyNameDescription": "Gi denne policynavnet for å identifisere den på tvers av dine ressurser",
@@ -314,7 +311,7 @@
"rules": "Regler", "rules": "Regler",
"resourceSettingDescription": "Konfigurere innstillingene på ressursen", "resourceSettingDescription": "Konfigurere innstillingene på ressursen",
"resourceSetting": "{resourceName} Innstillinger", "resourceSetting": "{resourceName} Innstillinger",
"resourcePolicySettingDescription": "Configure the settings on this public resource policy", "resourcePolicySettingDescription": "Konfigurer innstillingene på ressurspolitikken",
"resourcePolicySetting": "{policyName} Innstillinger", "resourcePolicySetting": "{policyName} Innstillinger",
"alwaysAllow": "Omgå Auth", "alwaysAllow": "Omgå Auth",
"alwaysDeny": "Blokker tilgang", "alwaysDeny": "Blokker tilgang",
@@ -756,11 +753,11 @@
"rulesErrorDuplicate": "Duplisert regel", "rulesErrorDuplicate": "Duplisert regel",
"rulesErrorDuplicateDescription": "En regel med disse innstillingene finnes allerede", "rulesErrorDuplicateDescription": "En regel med disse innstillingene finnes allerede",
"rulesErrorInvalidIpAddressRange": "Ugyldig CIDR", "rulesErrorInvalidIpAddressRange": "Ugyldig CIDR",
"rulesErrorInvalidIpAddressRangeDescription": "Enter a valid CIDR range (e.g., 10.0.0.0/8).", "rulesErrorInvalidIpAddressRangeDescription": "Vennligst skriv inn en gyldig CIDR-verdi",
"rulesErrorInvalidUrl": "Invalid path", "rulesErrorInvalidUrl": "Ugyldig URL-sti",
"rulesErrorInvalidUrlDescription": "Enter a valid URL path or pattern (e.g., /api/*).", "rulesErrorInvalidUrlDescription": "Skriv inn en gyldig verdi for URL-sti",
"rulesErrorInvalidIpAddress": "Invalid IP address", "rulesErrorInvalidIpAddress": "Ugyldig IP",
"rulesErrorInvalidIpAddressDescription": "Enter a valid IPv4 or IPv6 address.", "rulesErrorInvalidIpAddressDescription": "Skriv inn en gyldig IP-adresse",
"rulesErrorUpdate": "Kunne ikke oppdatere regler", "rulesErrorUpdate": "Kunne ikke oppdatere regler",
"rulesErrorUpdateDescription": "Det oppsto en feil under oppdatering av regler", "rulesErrorUpdateDescription": "Det oppsto en feil under oppdatering av regler",
"rulesUpdated": "Aktiver Regler", "rulesUpdated": "Aktiver Regler",
@@ -768,23 +765,15 @@
"rulesMatchIpAddressRangeDescription": "Angi en adresse i CIDR-format (f.eks., 103.21.244.0/22)", "rulesMatchIpAddressRangeDescription": "Angi en adresse i CIDR-format (f.eks., 103.21.244.0/22)",
"rulesMatchIpAddress": "Angi en IP-adresse (f.eks. 103.21.244.12)", "rulesMatchIpAddress": "Angi en IP-adresse (f.eks. 103.21.244.12)",
"rulesMatchUrl": "Skriv inn en URL-sti eller et mønster (f.eks. /api/v1/todos eller /api/v1/*)", "rulesMatchUrl": "Skriv inn en URL-sti eller et mønster (f.eks. /api/v1/todos eller /api/v1/*)",
"rulesErrorInvalidPriority": "Invalid priority", "rulesErrorInvalidPriority": "Ugyldig prioritet",
"rulesErrorInvalidPriorityDescription": "Enter a whole number of 1 or higher.", "rulesErrorInvalidPriorityDescription": "Vennligst skriv inn en gyldig prioritet",
"rulesErrorDuplicatePriority": "Duplicate priorities", "rulesErrorDuplicatePriority": "Dupliserte prioriteringer",
"rulesErrorDuplicatePriorityDescription": "Each rule must have a unique priority number.", "rulesErrorDuplicatePriorityDescription": "Vennligst angi unike prioriteringer",
"rulesErrorValidation": "Invalid rules",
"rulesErrorValidationRuleDescription": "Rule {ruleNumber}: {message}",
"rulesErrorValueRequired": "Enter a value for this rule.",
"rulesErrorInvalidCountry": "Invalid country",
"rulesErrorInvalidCountryDescription": "Select a valid country.",
"rulesErrorInvalidAsn": "Invalid ASN",
"rulesErrorInvalidAsnDescription": "Enter a valid ASN (e.g., AS15169).",
"ruleUpdated": "Regler oppdatert", "ruleUpdated": "Regler oppdatert",
"ruleUpdatedDescription": "Reglene er oppdatert", "ruleUpdatedDescription": "Reglene er oppdatert",
"ruleErrorUpdate": "Operasjon mislyktes", "ruleErrorUpdate": "Operasjon mislyktes",
"ruleErrorUpdateDescription": "En feil oppsto under lagringsoperasjonen", "ruleErrorUpdateDescription": "En feil oppsto under lagringsoperasjonen",
"rulesPriority": "Prioritet", "rulesPriority": "Prioritet",
"rulesReorderDragHandle": "Drag to reorder rule priority",
"rulesAction": "Handling", "rulesAction": "Handling",
"rulesMatchType": "Trefftype", "rulesMatchType": "Trefftype",
"value": "Verdi", "value": "Verdi",
@@ -803,7 +792,7 @@
"rulesResource": "Konfigurasjon av ressursregler", "rulesResource": "Konfigurasjon av ressursregler",
"rulesResourceDescription": "Konfigurer regler for å kontrollere tilgang til ressursen", "rulesResourceDescription": "Konfigurer regler for å kontrollere tilgang til ressursen",
"ruleSubmit": "Legg til regel", "ruleSubmit": "Legg til regel",
"rulesNoOne": "No rules yet.", "rulesNoOne": "Ingen regler. Legg til en regel ved å bruke skjemaet.",
"rulesOrder": "Regler evalueres etter prioritet i stigende rekkefølge.", "rulesOrder": "Regler evalueres etter prioritet i stigende rekkefølge.",
"rulesSubmit": "Lagre regler", "rulesSubmit": "Lagre regler",
"policyErrorCreate": "Feil ved opprettelse av policy", "policyErrorCreate": "Feil ved opprettelse av policy",
@@ -814,44 +803,7 @@
"policyErrorUpdateMessageDescription": "En uventet feil oppstod", "policyErrorUpdateMessageDescription": "En uventet feil oppstod",
"policyCreatedSuccess": "Ressurspolitikken ble opprettet vellykket", "policyCreatedSuccess": "Ressurspolitikken ble opprettet vellykket",
"policyUpdatedSuccess": "Ressurspolitikken ble oppdatert vellykket", "policyUpdatedSuccess": "Ressurspolitikken ble oppdatert vellykket",
"authMethodsSave": "Save Settings", "authMethodsSave": "Lagre autentiseringsmetoder",
"policyAuthStackTitle": "Authentication",
"policyAuthStackDescription": "Control which authentication methods are required to access this resource",
"policyAuthOrLogicTitle": "Multiple authentication methods active",
"policyAuthOrLogicBanner": "Visitors may authenticate using any one of the active methods below. They do not need to complete all of them.",
"policyAuthMethodActive": "Active",
"policyAuthMethodOff": "Off",
"policyAuthSsoTitle": "Platform SSO",
"policyAuthSsoDescription": "Require sign-in through your organization's identity provider",
"policyAuthSsoSummary": "{idp} · {users} users, {roles} roles",
"policyAuthSsoDefaultIdp": "Default provider",
"policyAuthAddDefaultIdentityProvider": "Add Default Identity Provider",
"policyAuthOtherMethodsTitle": "Other Methods",
"policyAuthOtherMethodsDescription": "Optional methods visitors can use instead of or alongside platform SSO",
"policyAuthPasscodeTitle": "Passcode",
"policyAuthPasscodeDescription": "Require a shared alphanumeric passcode to access the resource",
"policyAuthPasscodeSummary": "Passcode set",
"policyAuthPincodeTitle": "PIN Code",
"policyAuthPincodeDescription": "A short numeric code required to access the resource",
"policyAuthPincodeSummary": "6-digit PIN set",
"policyAuthEmailTitle": "Email Whitelist",
"policyAuthEmailDescription": "Allow listed email addresses with one-time passwords",
"policyAuthEmailSummary": "{count} addresses allowed",
"policyAuthEmailOtpCallout": "Enabling email whitelist sends a one-time password to the visitor's email on login.",
"policyAuthHeaderAuthTitle": "Basic Header Auth",
"policyAuthHeaderAuthDescription": "Validate a custom HTTP header name and value on each request",
"policyAuthHeaderAuthSummary": "Header configured",
"policyAuthHeaderName": "Header name",
"policyAuthHeaderValue": "Expected value",
"policyAccessRulesTitle": "Access Rules",
"policyAccessRulesEnableDescription": "When enabled, rules are evaluated in descending order until one evaluates as true.",
"policyAccessRulesFirstMatch": "Rules are evaluated top to bottom. The first matching rule decides the outcome.",
"policyAccessRulesHowItWorks": "Rules match requests by path, IP address, location, or other criteria. Each rule applies an action: bypass authentication, block access, or pass to authentication. If no rule matches, traffic continues to authentication.",
"policyAccessRulesFallthroughOff": "When rules are disabled, all traffic passes through to authentication.",
"policyAccessRulesFallthroughOn": "When no rule matches, traffic passes through to authentication.",
"rulesPlaceholderCidr": "10.0.0.0/8",
"rulesPlaceholderPath": "/admin/*",
"rulesPlaceholderGeo": "RU, KP",
"rulesSave": "Lagre Regler", "rulesSave": "Lagre Regler",
"resourceErrorCreate": "Feil under oppretting av ressurs", "resourceErrorCreate": "Feil under oppretting av ressurs",
"resourceErrorCreateDescription": "Det oppstod en feil under oppretting av ressursen", "resourceErrorCreateDescription": "Det oppstod en feil under oppretting av ressursen",
@@ -1268,10 +1220,8 @@
"addLabels": "Legg til etiketter", "addLabels": "Legg til etiketter",
"siteLabelsTab": "Etiketter", "siteLabelsTab": "Etiketter",
"siteLabelsDescription": "Administrer etiketter knyttet til dette nettstedet.", "siteLabelsDescription": "Administrer etiketter knyttet til dette nettstedet.",
"labelsNotFound": "No labels found.", "labelsNotFound": "Etiketter ikke funnet",
"labelsEmptyCreateHint": "Start typing above to create a label.",
"labelSearch": "Søk etter etiketter", "labelSearch": "Søk etter etiketter",
"labelSearchOrCreate": "Search or create a label",
"accessLabelFilterCount": "{count, plural, one {en etikett} other {# etiketter}}", "accessLabelFilterCount": "{count, plural, one {en etikett} other {# etiketter}}",
"labelOverflowCount": "+{count, plural, one {en etikett} other {# etiketter}}", "labelOverflowCount": "+{count, plural, one {en etikett} other {# etiketter}}",
"accessLabelFilterClear": "Fjern etikettfiltre", "accessLabelFilterClear": "Fjern etikettfiltre",
@@ -1512,7 +1462,7 @@
"sidebarProxyResources": "Offentlig", "sidebarProxyResources": "Offentlig",
"sidebarClientResources": "Privat", "sidebarClientResources": "Privat",
"sidebarPolicies": "Retningslinjer", "sidebarPolicies": "Retningslinjer",
"sidebarResourcePolicies": "Public Resources", "sidebarResourcePolicies": "Ressurser",
"sidebarAccessControl": "Tilgangskontroll", "sidebarAccessControl": "Tilgangskontroll",
"sidebarLogsAndAnalytics": "Logger og analyser", "sidebarLogsAndAnalytics": "Logger og analyser",
"sidebarTeam": "Lag", "sidebarTeam": "Lag",
@@ -1520,7 +1470,7 @@
"sidebarAdmin": "Administrator", "sidebarAdmin": "Administrator",
"sidebarInvitations": "Invitasjoner", "sidebarInvitations": "Invitasjoner",
"sidebarRoles": "Roller", "sidebarRoles": "Roller",
"sidebarShareableLinks": "Share Links", "sidebarShareableLinks": "Lenker",
"sidebarApiKeys": "API-nøkler", "sidebarApiKeys": "API-nøkler",
"sidebarProvisioning": "Levering", "sidebarProvisioning": "Levering",
"sidebarSettings": "Innstillinger", "sidebarSettings": "Innstillinger",
@@ -1697,7 +1647,7 @@
"standaloneHcFilterResourceIdFallback": "Ressurs {id}", "standaloneHcFilterResourceIdFallback": "Ressurs {id}",
"blueprints": "Tegninger", "blueprints": "Tegninger",
"blueprintsLog": "Blåkopieringslogg", "blueprintsLog": "Blåkopieringslogg",
"blueprintsDescription": "View past blueprint applications and their results or apply a new blueprint", "blueprintsDescription": "Vis tidligere applikasjoner av blåkopier og deres resultater",
"blueprintAdd": "Legg til blåkopi", "blueprintAdd": "Legg til blåkopi",
"blueprintGoBack": "Se alle blåkopier", "blueprintGoBack": "Se alle blåkopier",
"blueprintCreate": "Opprette mal", "blueprintCreate": "Opprette mal",
@@ -2077,13 +2027,13 @@
"healthCheckUnknown": "Ukjent", "healthCheckUnknown": "Ukjent",
"healthCheck": "Helsekontroll", "healthCheck": "Helsekontroll",
"configureHealthCheck": "Konfigurer Helsekontroll", "configureHealthCheck": "Konfigurer Helsekontroll",
"configureHealthCheckDescription": "Set up monitoring for your resource to ensure it is always available", "configureHealthCheckDescription": "Sett opp helsekontroll for {target}",
"enableHealthChecks": "Aktiver Helsekontroller", "enableHealthChecks": "Aktiver Helsekontroller",
"healthCheckDisabledStateDescription": "Når deaktivert, vil ikke nettstedet utføre helsekontroller, og tilstanden vil anses som ukjent.", "healthCheckDisabledStateDescription": "Når deaktivert, vil ikke nettstedet utføre helsekontroller, og tilstanden vil anses som ukjent.",
"enableHealthChecksDescription": "Overvåk helsen til dette målet. Du kan overvåke et annet endepunkt enn målet hvis nødvendig.", "enableHealthChecksDescription": "Overvåk helsen til dette målet. Du kan overvåke et annet endepunkt enn målet hvis nødvendig.",
"healthScheme": "Metode", "healthScheme": "Metode",
"healthSelectScheme": "Velg metode", "healthSelectScheme": "Velg metode",
"healthCheckPortInvalid": "Port must be between 1 and 65535", "healthCheckPortInvalid": "Helsekontrollporten må være mellom 1 og 65535",
"healthCheckPath": "Sti", "healthCheckPath": "Sti",
"healthHostname": "IP / Vert", "healthHostname": "IP / Vert",
"healthPort": "Port", "healthPort": "Port",
@@ -2096,7 +2046,6 @@
"requireDeviceApproval": "Krev enhetsgodkjenning", "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.", "requireDeviceApprovalDescription": "Brukere med denne rollen trenger nye enheter godkjent av en admin før de kan koble seg og få tilgang til ressurser.",
"sshSettings": "SSH Innstillinger", "sshSettings": "SSH Innstillinger",
"sshAccess": "SSH Access",
"rdpSettings": "RDP Innstillinger", "rdpSettings": "RDP Innstillinger",
"vncSettings": "VNC Innstillinger", "vncSettings": "VNC Innstillinger",
"sshServer": "SSH-server", "sshServer": "SSH-server",
@@ -2123,13 +2072,8 @@
"sshDaemonDisclaimer": "Sørg for at målenheten din er riktig konfigurert for å kjøre autentiseringsdaemon før du fullfører denne oppsettet, eller klargjøring vil mislykkes.", "sshDaemonDisclaimer": "Sørg for at målenheten din er riktig konfigurert for å kjøre autentiseringsdaemon før du fullfører denne oppsettet, eller klargjøring vil mislykkes.",
"sshDaemonPort": "Daemon-port", "sshDaemonPort": "Daemon-port",
"sshServerDestination": "Serverens Destinasjon", "sshServerDestination": "Serverens Destinasjon",
"sshServerDestinationDescription": "Configure the destination of the SSH server", "sshServerDestinationDescription": "Konfigurer destinasjonen og porten til SSH-serveren",
"destination": "Destinasjon", "destination": "Destinasjon",
"destinationRequired": "Destination is required.",
"domainRequired": "Domain is required.",
"proxyPortRequired": "Port is required.",
"invalidPathConfiguration": "Invalid path configuration.",
"invalidRewritePathConfiguration": "Invalid rewrite path configuration.",
"bgTargetMultiSiteDisclaimer": "Ved å velge flere nettsteder aktiveres robust ruting og feilaktig avbrudd for høy tilgjengelighet.", "bgTargetMultiSiteDisclaimer": "Ved å velge flere nettsteder aktiveres robust ruting og feilaktig avbrudd for høy tilgjengelighet.",
"roleAllowSsh": "Tillat SSH", "roleAllowSsh": "Tillat SSH",
"roleAllowSshAllow": "Tillat", "roleAllowSshAllow": "Tillat",
@@ -3090,7 +3034,7 @@
"enterConfirmation": "Skriv inn bekreftelse", "enterConfirmation": "Skriv inn bekreftelse",
"blueprintViewDetails": "Detaljer", "blueprintViewDetails": "Detaljer",
"defaultIdentityProvider": "Standard identitetsleverandør", "defaultIdentityProvider": "Standard identitetsleverandør",
"defaultIdentityProviderDescription": "The user will be automatically redirected to this identity provider for authentication.", "defaultIdentityProviderDescription": "Når en standard identitetsleverandør er valgt, vil brukeren automatisk bli omdirigert til leverandøren for autentisering.",
"editInternalResourceDialogNetworkSettings": "Nettverksinnstillinger", "editInternalResourceDialogNetworkSettings": "Nettverksinnstillinger",
"editInternalResourceDialogAccessPolicy": "Tilgangsregler for tilgang", "editInternalResourceDialogAccessPolicy": "Tilgangsregler for tilgang",
"editInternalResourceDialogAddRoles": "Legg til roller", "editInternalResourceDialogAddRoles": "Legg til roller",
@@ -3402,8 +3346,6 @@
"idpUnassociateQuestion": "Er du sikker på at du vil frakoble denne identitetsleverandøren fra denne organisasjonen?", "idpUnassociateQuestion": "Er du sikker på at du vil frakoble denne identitetsleverandøren fra denne organisasjonen?",
"idpUnassociateDescription": "Alle brukere knyttet til denne identitetsleverandøren vil bli fjernet fra denne organisasjonen, men identitetsleverandøren vil fortsatt eksistere for andre tilknyttede organisasjoner.", "idpUnassociateDescription": "Alle brukere knyttet til denne identitetsleverandøren vil bli fjernet fra denne organisasjonen, men identitetsleverandøren vil fortsatt eksistere for andre tilknyttede organisasjoner.",
"idpUnassociateConfirm": "Bekreft frakobling av identitetsleverandør", "idpUnassociateConfirm": "Bekreft frakobling av identitetsleverandør",
"idpConfirmDeleteAndRemoveMeFromOrg": "DELETE AND REMOVE ME FROM ORG",
"idpUnassociateAndRemoveMeFromOrg": "UNASSOCIATE AND REMOVE ME FROM ORG",
"idpUnassociateWarning": "Dette kan ikke angres for denne organisasjonen.", "idpUnassociateWarning": "Dette kan ikke angres for denne organisasjonen.",
"idpUnassociatedDescription": "Identitetsleverandør er vellykket frakoblet fra denne organisasjonen", "idpUnassociatedDescription": "Identitetsleverandør er vellykket frakoblet fra denne organisasjonen",
"idpUnassociateMenu": "Frakoble", "idpUnassociateMenu": "Frakoble",
@@ -3510,43 +3452,5 @@
"sshErrorNoTarget": "Ingen mål spesifisert", "sshErrorNoTarget": "Ingen mål spesifisert",
"sshErrorWebSocket": "WebSocket-tilkobling mislyktes", "sshErrorWebSocket": "WebSocket-tilkobling mislyktes",
"sshErrorAuthFailed": "Autentisering mislyktes", "sshErrorAuthFailed": "Autentisering mislyktes",
"sshErrorConnectionClosed": "Tilkobling avsluttet før autentisering ble fullført", "sshErrorConnectionClosed": "Tilkobling avsluttet før autentisering ble fullført"
"sitePangolinSshDescription": "Allow SSH access to resources on this site. This can be changed later.",
"browserGatewayNoResourceForDomain": "No resource found for this domain",
"browserGatewayNoTarget": "No target",
"browserGatewayConnect": "Connect",
"browserGatewayCtrlAltDel": "Ctrl+Alt+Del",
"sshErrorSignKeyFailed": "Failed to sign SSH key for PAM push authentication. Did you sign in as a user?",
"sshTerminalError": "Error: {error}",
"sshConnectionClosedCode": "Connection closed (code {code})",
"sshPrivateKeyPlaceholder": "-----BEGIN OPENSSH PRIVATE KEY-----",
"sshPrivateKeyRequired": "Private key is required",
"vncTitle": "VNC",
"vncSignInDescription": "Enter your VNC password to connect",
"vncPasswordOptional": "Password (optional)",
"vncNoResourceTarget": "No resource target is available",
"vncFailedToLoadNovnc": "Failed to load noVNC",
"vncAuthFailedStatus": "Status {status}",
"vncPasteClipboard": "Paste clipboard",
"rdpTitle": "RDP",
"rdpSignInTitle": "Sign in to Remote Desktop",
"rdpSignInDescription": "Enter Windows credentials to connect",
"rdpLoadingModule": "Loading module...",
"rdpFailedToLoadModule": "Failed to load RDP module",
"rdpNotReady": "Not ready",
"rdpModuleInitializing": "RDP module is still initializing",
"rdpDownloadingFiles": "Downloading {count} file(s) from remote…",
"rdpDownloadFailed": "Download failed: {fileName}",
"rdpUploaded": "Uploaded: {fileName}",
"rdpNoConnectionTarget": "No connection target available",
"rdpConnectionFailed": "Connection failed",
"rdpFit": "Fit",
"rdpFull": "Full",
"rdpReal": "Real",
"rdpMeta": "Meta",
"rdpUploadFiles": "Upload files",
"rdpFilesReadyToPaste": "Files ready to paste",
"rdpFilesReadyToPasteDescription": "{count} file(s) copied to remote clipboard — press Ctrl+V on the remote desktop to paste.",
"rdpUploadFailed": "Upload failed",
"rdpUnicodeKeyboardMode": "Unicode keyboard mode"
} }
+27 -123
View File
@@ -101,8 +101,6 @@
"sitesTableViewPrivateResources": "Privébronnen bekijken", "sitesTableViewPrivateResources": "Privébronnen bekijken",
"siteInstallNewt": "Installeer Newt", "siteInstallNewt": "Installeer Newt",
"siteInstallNewtDescription": "Laat Newt draaien op uw systeem", "siteInstallNewtDescription": "Laat Newt draaien op uw systeem",
"siteInstallKubernetesDocsDescription": "For more and up to date Kubernetes installation information, see <docsLink>docs.pangolin.net/manage/sites/install-kubernetes</docsLink>.",
"siteInstallAdvantechDocsDescription": "For Advantech modem installation instructions, see <docsLink>docs.pangolin.net/manage/sites/install-advantech</docsLink>.",
"WgConfiguration": "WireGuard Configuratie", "WgConfiguration": "WireGuard Configuratie",
"WgConfigurationDescription": "Gebruik de volgende configuratie om verbinding te maken met het netwerk", "WgConfigurationDescription": "Gebruik de volgende configuratie om verbinding te maken met het netwerk",
"operatingSystem": "Operating systeem", "operatingSystem": "Operating systeem",
@@ -202,8 +200,8 @@
"shareErrorSelectResource": "Selecteer een bron", "shareErrorSelectResource": "Selecteer een bron",
"proxyResourceTitle": "Openbare bronnen beheren", "proxyResourceTitle": "Openbare bronnen beheren",
"proxyResourceDescription": "Creëer en beheer bronnen die openbaar toegankelijk zijn via een webbrowser", "proxyResourceDescription": "Creëer en beheer bronnen die openbaar toegankelijk zijn via een webbrowser",
"publicResourcesBannerTitle": "Web-based Public Access", "publicResourcesBannerTitle": "Webgebaseerde openbare toegang",
"publicResourcesBannerDescription": "Public resources are HTTPS proxies accessible to anyone on the internet through a web browser. Unlike private resources, they do not require client-side software and can include identity and context-aware access policies.", "publicResourcesBannerDescription": "Openbare bronnen zijn HTTPS of TCP/UDP-proxies die toegankelijk zijn voor iedereen op het internet via een webbrowser. In tegenstelling tot privbronnen vereisen ze geen client-side software maar kunnen ze identiteits- en context-bewuste toegangsrichtlijnen bevatten.",
"clientResourceTitle": "Privébronnen beheren", "clientResourceTitle": "Privébronnen beheren",
"clientResourceDescription": "Creëer en beheer bronnen die alleen toegankelijk zijn via een verbonden client", "clientResourceDescription": "Creëer en beheer bronnen die alleen toegankelijk zijn via een verbonden client",
"privateResourcesBannerTitle": "Zero-Trust Private Access", "privateResourcesBannerTitle": "Zero-Trust Private Access",
@@ -211,16 +209,15 @@
"resourcesSearch": "Zoek bronnen...", "resourcesSearch": "Zoek bronnen...",
"resourceAdd": "Bron toevoegen", "resourceAdd": "Bron toevoegen",
"resourceErrorDelte": "Fout bij verwijderen document", "resourceErrorDelte": "Fout bij verwijderen document",
"resourcePoliciesTitle": "Manage Public Resource Policies", "resourcePoliciesTitle": "Beheer Bron Beleid",
"resourcePoliciesAttachedResourcesColumnTitle": "Resources", "resourcePoliciesAttachedResourcesColumnTitle": "Bijgevoegde bronnen",
"resourcePoliciesAttachedResources": "{count} bron(nen)", "resourcePoliciesAttachedResources": "{count} bron(nen)",
"resourcePoliciesAttachedResourcesCount": "{count, plural, one {# resource} other {# resources}}",
"resourcePoliciesAttachedResourcesEmpty": "geen bronnen", "resourcePoliciesAttachedResourcesEmpty": "geen bronnen",
"resourcePoliciesDescription": "Create and manage authentication policies to control access to your public resources", "resourcePoliciesDescription": "Maak en beheer authenticatiebeleid om toegang tot uw bronnen te controleren",
"resourcePoliciesSearch": "Beleidsregels zoeken...", "resourcePoliciesSearch": "Beleidsregels zoeken...",
"resourcePoliciesAdd": "Beleid toevoegen", "resourcePoliciesAdd": "Beleid toevoegen",
"resourcePoliciesDefaultBadgeText": "Standaard beleidsregel", "resourcePoliciesDefaultBadgeText": "Standaard beleidsregel",
"resourcePoliciesCreate": "Create Public Resource Policy", "resourcePoliciesCreate": "Creëer Bronbeleid",
"resourcePoliciesCreateDescription": "Volg de onderstaande stappen om een nieuw beleid aan te maken", "resourcePoliciesCreateDescription": "Volg de onderstaande stappen om een nieuw beleid aan te maken",
"resourcePolicyName": "Beleidsregelnaam", "resourcePolicyName": "Beleidsregelnaam",
"resourcePolicyNameDescription": "Geef deze beleidsregel een naam om deze te identificeren in uw bronnen", "resourcePolicyNameDescription": "Geef deze beleidsregel een naam om deze te identificeren in uw bronnen",
@@ -314,7 +311,7 @@
"rules": "Regels", "rules": "Regels",
"resourceSettingDescription": "Configureer de instellingen in de bron", "resourceSettingDescription": "Configureer de instellingen in de bron",
"resourceSetting": "{resourceName} instellingen", "resourceSetting": "{resourceName} instellingen",
"resourcePolicySettingDescription": "Configure the settings on this public resource policy", "resourcePolicySettingDescription": "Configureer de instellingen op het bronbeleid",
"resourcePolicySetting": "{policyName} instellingen", "resourcePolicySetting": "{policyName} instellingen",
"alwaysAllow": "Authenticatie omzeilen", "alwaysAllow": "Authenticatie omzeilen",
"alwaysDeny": "Blokkeer toegang", "alwaysDeny": "Blokkeer toegang",
@@ -756,11 +753,11 @@
"rulesErrorDuplicate": "Dupliceer regel", "rulesErrorDuplicate": "Dupliceer regel",
"rulesErrorDuplicateDescription": "Een regel met deze instellingen bestaat al", "rulesErrorDuplicateDescription": "Een regel met deze instellingen bestaat al",
"rulesErrorInvalidIpAddressRange": "Ongeldige CIDR", "rulesErrorInvalidIpAddressRange": "Ongeldige CIDR",
"rulesErrorInvalidIpAddressRangeDescription": "Enter a valid CIDR range (e.g., 10.0.0.0/8).", "rulesErrorInvalidIpAddressRangeDescription": "Voer een geldige CIDR waarde in",
"rulesErrorInvalidUrl": "Invalid path", "rulesErrorInvalidUrl": "Ongeldige URL pad",
"rulesErrorInvalidUrlDescription": "Enter a valid URL path or pattern (e.g., /api/*).", "rulesErrorInvalidUrlDescription": "Voer een geldige URL padwaarde in",
"rulesErrorInvalidIpAddress": "Invalid IP address", "rulesErrorInvalidIpAddress": "Ongeldig IP",
"rulesErrorInvalidIpAddressDescription": "Enter a valid IPv4 or IPv6 address.", "rulesErrorInvalidIpAddressDescription": "Voer een geldig IP-adres in",
"rulesErrorUpdate": "Regels bijwerken mislukt", "rulesErrorUpdate": "Regels bijwerken mislukt",
"rulesErrorUpdateDescription": "Fout opgetreden tijdens het bijwerken van de regels", "rulesErrorUpdateDescription": "Fout opgetreden tijdens het bijwerken van de regels",
"rulesUpdated": "Regels inschakelen", "rulesUpdated": "Regels inschakelen",
@@ -768,23 +765,15 @@
"rulesMatchIpAddressRangeDescription": "Voer een adres in in het CIDR-formaat (bijv. 103.21.244.0/22)", "rulesMatchIpAddressRangeDescription": "Voer een adres in in het CIDR-formaat (bijv. 103.21.244.0/22)",
"rulesMatchIpAddress": "Voer een IP-adres in (bijv. 103.21.244.12)", "rulesMatchIpAddress": "Voer een IP-adres in (bijv. 103.21.244.12)",
"rulesMatchUrl": "Voer een URL-pad of patroon in (bijv. /api/v1/todos of /api/v1/*)", "rulesMatchUrl": "Voer een URL-pad of patroon in (bijv. /api/v1/todos of /api/v1/*)",
"rulesErrorInvalidPriority": "Invalid priority", "rulesErrorInvalidPriority": "Ongeldige prioriteit",
"rulesErrorInvalidPriorityDescription": "Enter a whole number of 1 or higher.", "rulesErrorInvalidPriorityDescription": "Voer een geldige prioriteit in",
"rulesErrorDuplicatePriority": "Duplicate priorities", "rulesErrorDuplicatePriority": "Dubbele prioriteiten",
"rulesErrorDuplicatePriorityDescription": "Each rule must have a unique priority number.", "rulesErrorDuplicatePriorityDescription": "Voer unieke prioriteiten in",
"rulesErrorValidation": "Invalid rules",
"rulesErrorValidationRuleDescription": "Rule {ruleNumber}: {message}",
"rulesErrorValueRequired": "Enter a value for this rule.",
"rulesErrorInvalidCountry": "Invalid country",
"rulesErrorInvalidCountryDescription": "Select a valid country.",
"rulesErrorInvalidAsn": "Invalid ASN",
"rulesErrorInvalidAsnDescription": "Enter a valid ASN (e.g., AS15169).",
"ruleUpdated": "Regels bijgewerkt", "ruleUpdated": "Regels bijgewerkt",
"ruleUpdatedDescription": "Regels met succes bijgewerkt", "ruleUpdatedDescription": "Regels met succes bijgewerkt",
"ruleErrorUpdate": "Bewerking mislukt", "ruleErrorUpdate": "Bewerking mislukt",
"ruleErrorUpdateDescription": "Er is een fout opgetreden tijdens het opslaan", "ruleErrorUpdateDescription": "Er is een fout opgetreden tijdens het opslaan",
"rulesPriority": "Prioriteit", "rulesPriority": "Prioriteit",
"rulesReorderDragHandle": "Drag to reorder rule priority",
"rulesAction": "actie", "rulesAction": "actie",
"rulesMatchType": "Wedstrijd Type", "rulesMatchType": "Wedstrijd Type",
"value": "Waarde", "value": "Waarde",
@@ -803,7 +792,7 @@
"rulesResource": "Configuratie Resource Regels", "rulesResource": "Configuratie Resource Regels",
"rulesResourceDescription": "Regels instellen om toegang tot de bron te beheren", "rulesResourceDescription": "Regels instellen om toegang tot de bron te beheren",
"ruleSubmit": "Regel toevoegen", "ruleSubmit": "Regel toevoegen",
"rulesNoOne": "No rules yet.", "rulesNoOne": "Geen regels. Voeg een regel toe via het formulier.",
"rulesOrder": "Regels worden in oplopende volgorde volgens prioriteit beoordeeld.", "rulesOrder": "Regels worden in oplopende volgorde volgens prioriteit beoordeeld.",
"rulesSubmit": "Regels opslaan", "rulesSubmit": "Regels opslaan",
"policyErrorCreate": "Fout bij het maken van beleid", "policyErrorCreate": "Fout bij het maken van beleid",
@@ -814,44 +803,7 @@
"policyErrorUpdateMessageDescription": "Er is een onverwachte fout opgetreden", "policyErrorUpdateMessageDescription": "Er is een onverwachte fout opgetreden",
"policyCreatedSuccess": "Bronbeleid met succes aangemaakt", "policyCreatedSuccess": "Bronbeleid met succes aangemaakt",
"policyUpdatedSuccess": "Bronbeleid succesvol bijgewerkt", "policyUpdatedSuccess": "Bronbeleid succesvol bijgewerkt",
"authMethodsSave": "Save Settings", "authMethodsSave": "Bewaar authenticatiemethoden",
"policyAuthStackTitle": "Authentication",
"policyAuthStackDescription": "Control which authentication methods are required to access this resource",
"policyAuthOrLogicTitle": "Multiple authentication methods active",
"policyAuthOrLogicBanner": "Visitors may authenticate using any one of the active methods below. They do not need to complete all of them.",
"policyAuthMethodActive": "Active",
"policyAuthMethodOff": "Off",
"policyAuthSsoTitle": "Platform SSO",
"policyAuthSsoDescription": "Require sign-in through your organization's identity provider",
"policyAuthSsoSummary": "{idp} · {users} users, {roles} roles",
"policyAuthSsoDefaultIdp": "Default provider",
"policyAuthAddDefaultIdentityProvider": "Add Default Identity Provider",
"policyAuthOtherMethodsTitle": "Other Methods",
"policyAuthOtherMethodsDescription": "Optional methods visitors can use instead of or alongside platform SSO",
"policyAuthPasscodeTitle": "Passcode",
"policyAuthPasscodeDescription": "Require a shared alphanumeric passcode to access the resource",
"policyAuthPasscodeSummary": "Passcode set",
"policyAuthPincodeTitle": "PIN Code",
"policyAuthPincodeDescription": "A short numeric code required to access the resource",
"policyAuthPincodeSummary": "6-digit PIN set",
"policyAuthEmailTitle": "Email Whitelist",
"policyAuthEmailDescription": "Allow listed email addresses with one-time passwords",
"policyAuthEmailSummary": "{count} addresses allowed",
"policyAuthEmailOtpCallout": "Enabling email whitelist sends a one-time password to the visitor's email on login.",
"policyAuthHeaderAuthTitle": "Basic Header Auth",
"policyAuthHeaderAuthDescription": "Validate a custom HTTP header name and value on each request",
"policyAuthHeaderAuthSummary": "Header configured",
"policyAuthHeaderName": "Header name",
"policyAuthHeaderValue": "Expected value",
"policyAccessRulesTitle": "Access Rules",
"policyAccessRulesEnableDescription": "When enabled, rules are evaluated in descending order until one evaluates as true.",
"policyAccessRulesFirstMatch": "Rules are evaluated top to bottom. The first matching rule decides the outcome.",
"policyAccessRulesHowItWorks": "Rules match requests by path, IP address, location, or other criteria. Each rule applies an action: bypass authentication, block access, or pass to authentication. If no rule matches, traffic continues to authentication.",
"policyAccessRulesFallthroughOff": "When rules are disabled, all traffic passes through to authentication.",
"policyAccessRulesFallthroughOn": "When no rule matches, traffic passes through to authentication.",
"rulesPlaceholderCidr": "10.0.0.0/8",
"rulesPlaceholderPath": "/admin/*",
"rulesPlaceholderGeo": "RU, KP",
"rulesSave": "Regels opslaan", "rulesSave": "Regels opslaan",
"resourceErrorCreate": "Fout bij maken document", "resourceErrorCreate": "Fout bij maken document",
"resourceErrorCreateDescription": "Er is een fout opgetreden bij het maken van het document", "resourceErrorCreateDescription": "Er is een fout opgetreden bij het maken van het document",
@@ -1268,10 +1220,8 @@
"addLabels": "Labels toevoegen", "addLabels": "Labels toevoegen",
"siteLabelsTab": "Labels", "siteLabelsTab": "Labels",
"siteLabelsDescription": "Beheer labels geassocieerd met deze site.", "siteLabelsDescription": "Beheer labels geassocieerd met deze site.",
"labelsNotFound": "No labels found.", "labelsNotFound": "Labels niet gevonden",
"labelsEmptyCreateHint": "Start typing above to create a label.",
"labelSearch": "Labels zoeken", "labelSearch": "Labels zoeken",
"labelSearchOrCreate": "Search or create a label",
"accessLabelFilterCount": "{count, plural, one {# label} other {# labels}}", "accessLabelFilterCount": "{count, plural, one {# label} other {# labels}}",
"labelOverflowCount": "+{count, plural, one {# label} other {# labels}}", "labelOverflowCount": "+{count, plural, one {# label} other {# labels}}",
"accessLabelFilterClear": "Labelfilters wissen", "accessLabelFilterClear": "Labelfilters wissen",
@@ -1512,7 +1462,7 @@
"sidebarProxyResources": "Openbaar", "sidebarProxyResources": "Openbaar",
"sidebarClientResources": "Privé", "sidebarClientResources": "Privé",
"sidebarPolicies": "Beleid", "sidebarPolicies": "Beleid",
"sidebarResourcePolicies": "Public Resources", "sidebarResourcePolicies": "Bronnen",
"sidebarAccessControl": "Toegangs controle", "sidebarAccessControl": "Toegangs controle",
"sidebarLogsAndAnalytics": "Logs & Analytics", "sidebarLogsAndAnalytics": "Logs & Analytics",
"sidebarTeam": "Team", "sidebarTeam": "Team",
@@ -1520,7 +1470,7 @@
"sidebarAdmin": "Beheerder", "sidebarAdmin": "Beheerder",
"sidebarInvitations": "Uitnodigingen", "sidebarInvitations": "Uitnodigingen",
"sidebarRoles": "Rollen", "sidebarRoles": "Rollen",
"sidebarShareableLinks": "Share Links", "sidebarShareableLinks": "Koppelingen",
"sidebarApiKeys": "API sleutels", "sidebarApiKeys": "API sleutels",
"sidebarProvisioning": "Provisie", "sidebarProvisioning": "Provisie",
"sidebarSettings": "Instellingen", "sidebarSettings": "Instellingen",
@@ -1697,7 +1647,7 @@
"standaloneHcFilterResourceIdFallback": "Bron {id}", "standaloneHcFilterResourceIdFallback": "Bron {id}",
"blueprints": "Blauwdrukken", "blueprints": "Blauwdrukken",
"blueprintsLog": "Log Blueprints", "blueprintsLog": "Log Blueprints",
"blueprintsDescription": "View past blueprint applications and their results or apply a new blueprint", "blueprintsDescription": "Bekijk eerdere blueprint-toepassingen en hun resultaten",
"blueprintAdd": "Blauwdruk toevoegen", "blueprintAdd": "Blauwdruk toevoegen",
"blueprintGoBack": "Bekijk alle Blauwdrukken", "blueprintGoBack": "Bekijk alle Blauwdrukken",
"blueprintCreate": "Creëer blauwdruk", "blueprintCreate": "Creëer blauwdruk",
@@ -2077,13 +2027,13 @@
"healthCheckUnknown": "Onbekend", "healthCheckUnknown": "Onbekend",
"healthCheck": "Gezondheidscontrole", "healthCheck": "Gezondheidscontrole",
"configureHealthCheck": "Configureer Gezondheidscontrole", "configureHealthCheck": "Configureer Gezondheidscontrole",
"configureHealthCheckDescription": "Set up monitoring for your resource to ensure it is always available", "configureHealthCheckDescription": "Stel gezondheid monitor voor {target} in",
"enableHealthChecks": "Inschakelen Gezondheidscontroles", "enableHealthChecks": "Inschakelen Gezondheidscontroles",
"healthCheckDisabledStateDescription": "Wanneer uitgeschakeld, zal de site geen gezondheidscontroles uitvoeren en wordt de staat als onbekend beschouwd.", "healthCheckDisabledStateDescription": "Wanneer uitgeschakeld, zal de site geen gezondheidscontroles uitvoeren en wordt de staat als onbekend beschouwd.",
"enableHealthChecksDescription": "Controleer de gezondheid van dit doel. U kunt een ander eindpunt monitoren dan het doel indien vereist.", "enableHealthChecksDescription": "Controleer de gezondheid van dit doel. U kunt een ander eindpunt monitoren dan het doel indien vereist.",
"healthScheme": "Methode", "healthScheme": "Methode",
"healthSelectScheme": "Selecteer methode", "healthSelectScheme": "Selecteer methode",
"healthCheckPortInvalid": "Port must be between 1 and 65535", "healthCheckPortInvalid": "Health check poort moet tussen 1 en 65535 zijn",
"healthCheckPath": "Pad", "healthCheckPath": "Pad",
"healthHostname": "IP / Hostnaam", "healthHostname": "IP / Hostnaam",
"healthPort": "Poort", "healthPort": "Poort",
@@ -2096,7 +2046,6 @@
"requireDeviceApproval": "Vereist goedkeuring van apparaat", "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.", "requireDeviceApprovalDescription": "Gebruikers met deze rol hebben nieuwe apparaten nodig die door een beheerder zijn goedgekeurd voordat ze verbinding kunnen maken met bronnen en deze kunnen gebruiken.",
"sshSettings": "SSH-instellingen", "sshSettings": "SSH-instellingen",
"sshAccess": "SSH Access",
"rdpSettings": "RDP-instellingen", "rdpSettings": "RDP-instellingen",
"vncSettings": "VNC-instellingen", "vncSettings": "VNC-instellingen",
"sshServer": "SSH-server", "sshServer": "SSH-server",
@@ -2123,13 +2072,8 @@
"sshDaemonDisclaimer": "Zorg ervoor dat uw doelhost goed is geconfigureerd om de auth daemon uit te voeren voordat u deze configuratie voltooit, anders zal de voorziening mislukken.", "sshDaemonDisclaimer": "Zorg ervoor dat uw doelhost goed is geconfigureerd om de auth daemon uit te voeren voordat u deze configuratie voltooit, anders zal de voorziening mislukken.",
"sshDaemonPort": "Demonpoort", "sshDaemonPort": "Demonpoort",
"sshServerDestination": "Serverbestemming", "sshServerDestination": "Serverbestemming",
"sshServerDestinationDescription": "Configure the destination of the SSH server", "sshServerDestinationDescription": "Configureer de bestemming en poort van de SSH-server",
"destination": "Bestemming", "destination": "Bestemming",
"destinationRequired": "Destination is required.",
"domainRequired": "Domain is required.",
"proxyPortRequired": "Port is required.",
"invalidPathConfiguration": "Invalid path configuration.",
"invalidRewritePathConfiguration": "Invalid rewrite path configuration.",
"bgTargetMultiSiteDisclaimer": "Het selecteren van meerdere sites maakt veerkrachtige routering mogelijk en failover voor hoge beschikbaarheid.", "bgTargetMultiSiteDisclaimer": "Het selecteren van meerdere sites maakt veerkrachtige routering mogelijk en failover voor hoge beschikbaarheid.",
"roleAllowSsh": "SSH toestaan", "roleAllowSsh": "SSH toestaan",
"roleAllowSshAllow": "Toestaan", "roleAllowSshAllow": "Toestaan",
@@ -3090,7 +3034,7 @@
"enterConfirmation": "Bevestiging invoeren", "enterConfirmation": "Bevestiging invoeren",
"blueprintViewDetails": "Details", "blueprintViewDetails": "Details",
"defaultIdentityProvider": "Standaard Identiteitsprovider", "defaultIdentityProvider": "Standaard Identiteitsprovider",
"defaultIdentityProviderDescription": "The user will be automatically redirected to this identity provider for authentication.", "defaultIdentityProviderDescription": "Wanneer een standaard identity provider is geselecteerd, zal de gebruiker automatisch worden doorgestuurd naar de provider voor authenticatie.",
"editInternalResourceDialogNetworkSettings": "Netwerkinstellingen", "editInternalResourceDialogNetworkSettings": "Netwerkinstellingen",
"editInternalResourceDialogAccessPolicy": "Toegangsbeleid", "editInternalResourceDialogAccessPolicy": "Toegangsbeleid",
"editInternalResourceDialogAddRoles": "Rollen toevoegen", "editInternalResourceDialogAddRoles": "Rollen toevoegen",
@@ -3402,8 +3346,6 @@
"idpUnassociateQuestion": "Weet u zeker dat u deze identiteitsprovider van deze organisatie wilt loskoppelen?", "idpUnassociateQuestion": "Weet u zeker dat u deze identiteitsprovider van deze organisatie wilt loskoppelen?",
"idpUnassociateDescription": "Alle gebruikers die aan deze identiteitsprovider zijn gekoppeld, worden uit deze organisatie verwijderd, maar de identiteitsprovider blijft bestaan voor andere gerelateerde organisaties.", "idpUnassociateDescription": "Alle gebruikers die aan deze identiteitsprovider zijn gekoppeld, worden uit deze organisatie verwijderd, maar de identiteitsprovider blijft bestaan voor andere gerelateerde organisaties.",
"idpUnassociateConfirm": "Bevestig ontkoppelen identiteitsprovider", "idpUnassociateConfirm": "Bevestig ontkoppelen identiteitsprovider",
"idpConfirmDeleteAndRemoveMeFromOrg": "DELETE AND REMOVE ME FROM ORG",
"idpUnassociateAndRemoveMeFromOrg": "UNASSOCIATE AND REMOVE ME FROM ORG",
"idpUnassociateWarning": "Dit kan niet ongedaan worden gemaakt voor deze organisatie.", "idpUnassociateWarning": "Dit kan niet ongedaan worden gemaakt voor deze organisatie.",
"idpUnassociatedDescription": "Identiteitsprovider succesvol losgekoppeld van deze organisatie", "idpUnassociatedDescription": "Identiteitsprovider succesvol losgekoppeld van deze organisatie",
"idpUnassociateMenu": "Ontkoppelen", "idpUnassociateMenu": "Ontkoppelen",
@@ -3510,43 +3452,5 @@
"sshErrorNoTarget": "Geen doelwit gespecificeerd", "sshErrorNoTarget": "Geen doelwit gespecificeerd",
"sshErrorWebSocket": "WebSocket-verbinding is mislukt", "sshErrorWebSocket": "WebSocket-verbinding is mislukt",
"sshErrorAuthFailed": "Authenticatie mislukt", "sshErrorAuthFailed": "Authenticatie mislukt",
"sshErrorConnectionClosed": "Verbinding gesloten voordat authenticatie was voltooid", "sshErrorConnectionClosed": "Verbinding gesloten voordat authenticatie was voltooid"
"sitePangolinSshDescription": "Allow SSH access to resources on this site. This can be changed later.",
"browserGatewayNoResourceForDomain": "No resource found for this domain",
"browserGatewayNoTarget": "No target",
"browserGatewayConnect": "Connect",
"browserGatewayCtrlAltDel": "Ctrl+Alt+Del",
"sshErrorSignKeyFailed": "Failed to sign SSH key for PAM push authentication. Did you sign in as a user?",
"sshTerminalError": "Error: {error}",
"sshConnectionClosedCode": "Connection closed (code {code})",
"sshPrivateKeyPlaceholder": "-----BEGIN OPENSSH PRIVATE KEY-----",
"sshPrivateKeyRequired": "Private key is required",
"vncTitle": "VNC",
"vncSignInDescription": "Enter your VNC password to connect",
"vncPasswordOptional": "Password (optional)",
"vncNoResourceTarget": "No resource target is available",
"vncFailedToLoadNovnc": "Failed to load noVNC",
"vncAuthFailedStatus": "Status {status}",
"vncPasteClipboard": "Paste clipboard",
"rdpTitle": "RDP",
"rdpSignInTitle": "Sign in to Remote Desktop",
"rdpSignInDescription": "Enter Windows credentials to connect",
"rdpLoadingModule": "Loading module...",
"rdpFailedToLoadModule": "Failed to load RDP module",
"rdpNotReady": "Not ready",
"rdpModuleInitializing": "RDP module is still initializing",
"rdpDownloadingFiles": "Downloading {count} file(s) from remote…",
"rdpDownloadFailed": "Download failed: {fileName}",
"rdpUploaded": "Uploaded: {fileName}",
"rdpNoConnectionTarget": "No connection target available",
"rdpConnectionFailed": "Connection failed",
"rdpFit": "Fit",
"rdpFull": "Full",
"rdpReal": "Real",
"rdpMeta": "Meta",
"rdpUploadFiles": "Upload files",
"rdpFilesReadyToPaste": "Files ready to paste",
"rdpFilesReadyToPasteDescription": "{count} file(s) copied to remote clipboard — press Ctrl+V on the remote desktop to paste.",
"rdpUploadFailed": "Upload failed",
"rdpUnicodeKeyboardMode": "Unicode keyboard mode"
} }
+27 -123
View File
@@ -101,8 +101,6 @@
"sitesTableViewPrivateResources": "Zobacz zasoby prywatne", "sitesTableViewPrivateResources": "Zobacz zasoby prywatne",
"siteInstallNewt": "Zainstaluj Newt", "siteInstallNewt": "Zainstaluj Newt",
"siteInstallNewtDescription": "Uruchom Newt w swoim systemie", "siteInstallNewtDescription": "Uruchom Newt w swoim systemie",
"siteInstallKubernetesDocsDescription": "For more and up to date Kubernetes installation information, see <docsLink>docs.pangolin.net/manage/sites/install-kubernetes</docsLink>.",
"siteInstallAdvantechDocsDescription": "For Advantech modem installation instructions, see <docsLink>docs.pangolin.net/manage/sites/install-advantech</docsLink>.",
"WgConfiguration": "Konfiguracja WireGuard", "WgConfiguration": "Konfiguracja WireGuard",
"WgConfigurationDescription": "Użyj następującej konfiguracji, aby połączyć się z siecią", "WgConfigurationDescription": "Użyj następującej konfiguracji, aby połączyć się z siecią",
"operatingSystem": "System operacyjny", "operatingSystem": "System operacyjny",
@@ -202,8 +200,8 @@
"shareErrorSelectResource": "Wybierz zasób", "shareErrorSelectResource": "Wybierz zasób",
"proxyResourceTitle": "Zarządzaj zasobami publicznymi", "proxyResourceTitle": "Zarządzaj zasobami publicznymi",
"proxyResourceDescription": "Twórz i zarządzaj zasobami, które są publicznie dostępne w przeglądarce internetowej", "proxyResourceDescription": "Twórz i zarządzaj zasobami, które są publicznie dostępne w przeglądarce internetowej",
"publicResourcesBannerTitle": "Web-based Public Access", "publicResourcesBannerTitle": "Publiczny dostęp za pośrednictwem sieci Web",
"publicResourcesBannerDescription": "Public resources are HTTPS proxies accessible to anyone on the internet through a web browser. Unlike private resources, they do not require client-side software and can include identity and context-aware access policies.", "publicResourcesBannerDescription": "Zasoby publiczne to proxy HTTPS lub TCP/UDP dostępne dla każdego w internecie za pośrednictwem przeglądarki internetowej. W przeciwieństwie do zasobów prywatnych, nie wymagają oprogramowania po stronie klienta i mogą obejmować polityki dostępu świadome tożsamości i kontekstu.",
"clientResourceTitle": "Zarządzaj zasobami prywatnymi", "clientResourceTitle": "Zarządzaj zasobami prywatnymi",
"clientResourceDescription": "Twórz i zarządzaj zasobami, które są dostępne tylko za pośrednictwem połączonego klienta", "clientResourceDescription": "Twórz i zarządzaj zasobami, które są dostępne tylko za pośrednictwem połączonego klienta",
"privateResourcesBannerTitle": "Zero zaufania do prywatnego dostępu", "privateResourcesBannerTitle": "Zero zaufania do prywatnego dostępu",
@@ -211,16 +209,15 @@
"resourcesSearch": "Szukaj zasobów...", "resourcesSearch": "Szukaj zasobów...",
"resourceAdd": "Dodaj zasób", "resourceAdd": "Dodaj zasób",
"resourceErrorDelte": "Błąd podczas usuwania zasobu", "resourceErrorDelte": "Błąd podczas usuwania zasobu",
"resourcePoliciesTitle": "Manage Public Resource Policies", "resourcePoliciesTitle": "Zarządzaj politykami zasobów",
"resourcePoliciesAttachedResourcesColumnTitle": "Resources", "resourcePoliciesAttachedResourcesColumnTitle": "Dołączone zasoby",
"resourcePoliciesAttachedResources": "{count} zasób(y)", "resourcePoliciesAttachedResources": "{count} zasób(y)",
"resourcePoliciesAttachedResourcesCount": "{count, plural, one {# resource} other {# resources}}",
"resourcePoliciesAttachedResourcesEmpty": "brak zasobów", "resourcePoliciesAttachedResourcesEmpty": "brak zasobów",
"resourcePoliciesDescription": "Create and manage authentication policies to control access to your public resources", "resourcePoliciesDescription": "Twórz i zarządzaj politykami uwierzytelniania, aby kontrolować dostęp do swoich zasobów",
"resourcePoliciesSearch": "Szukaj polityk...", "resourcePoliciesSearch": "Szukaj polityk...",
"resourcePoliciesAdd": "Dodaj politykę", "resourcePoliciesAdd": "Dodaj politykę",
"resourcePoliciesDefaultBadgeText": "Domyślna polityka", "resourcePoliciesDefaultBadgeText": "Domyślna polityka",
"resourcePoliciesCreate": "Create Public Resource Policy", "resourcePoliciesCreate": "Utwórz politykę zasobu",
"resourcePoliciesCreateDescription": "Wykonaj poniższe kroki, aby utworzyć nową politykę", "resourcePoliciesCreateDescription": "Wykonaj poniższe kroki, aby utworzyć nową politykę",
"resourcePolicyName": "Nazwa polityki", "resourcePolicyName": "Nazwa polityki",
"resourcePolicyNameDescription": "Nadaj tej polityce nazwę, aby można ją było zidentyfikować w całych zasobach", "resourcePolicyNameDescription": "Nadaj tej polityce nazwę, aby można ją było zidentyfikować w całych zasobach",
@@ -314,7 +311,7 @@
"rules": "Regulamin", "rules": "Regulamin",
"resourceSettingDescription": "Skonfiguruj ustawienia zasobu", "resourceSettingDescription": "Skonfiguruj ustawienia zasobu",
"resourceSetting": "Ustawienia {resourceName}", "resourceSetting": "Ustawienia {resourceName}",
"resourcePolicySettingDescription": "Configure the settings on this public resource policy", "resourcePolicySettingDescription": "Skonfiguruj ustawienia w polityce zasobów",
"resourcePolicySetting": "Ustawienia {policyName}", "resourcePolicySetting": "Ustawienia {policyName}",
"alwaysAllow": "Omijanie uwierzytelniania", "alwaysAllow": "Omijanie uwierzytelniania",
"alwaysDeny": "Blokuj dostęp", "alwaysDeny": "Blokuj dostęp",
@@ -756,11 +753,11 @@
"rulesErrorDuplicate": "Duplikat reguły", "rulesErrorDuplicate": "Duplikat reguły",
"rulesErrorDuplicateDescription": "Reguła o tych ustawieniach już istnieje", "rulesErrorDuplicateDescription": "Reguła o tych ustawieniach już istnieje",
"rulesErrorInvalidIpAddressRange": "Nieprawidłowy CIDR", "rulesErrorInvalidIpAddressRange": "Nieprawidłowy CIDR",
"rulesErrorInvalidIpAddressRangeDescription": "Enter a valid CIDR range (e.g., 10.0.0.0/8).", "rulesErrorInvalidIpAddressRangeDescription": "Wprowadź prawidłową wartość CIDR",
"rulesErrorInvalidUrl": "Invalid path", "rulesErrorInvalidUrl": "Nieprawidłowa ścieżka URL",
"rulesErrorInvalidUrlDescription": "Enter a valid URL path or pattern (e.g., /api/*).", "rulesErrorInvalidUrlDescription": "Wprowadź prawidłową wartość ścieżki URL",
"rulesErrorInvalidIpAddress": "Invalid IP address", "rulesErrorInvalidIpAddress": "Nieprawidłowe IP",
"rulesErrorInvalidIpAddressDescription": "Enter a valid IPv4 or IPv6 address.", "rulesErrorInvalidIpAddressDescription": "Wprowadź prawidłowy adres IP",
"rulesErrorUpdate": "Nie udało się zaktualizować reguł", "rulesErrorUpdate": "Nie udało się zaktualizować reguł",
"rulesErrorUpdateDescription": "Wystąpił błąd podczas aktualizacji reguł", "rulesErrorUpdateDescription": "Wystąpił błąd podczas aktualizacji reguł",
"rulesUpdated": "Włącz reguły", "rulesUpdated": "Włącz reguły",
@@ -768,23 +765,15 @@
"rulesMatchIpAddressRangeDescription": "Wprowadź adres w formacie CIDR (np. 103.21.244.0/22)", "rulesMatchIpAddressRangeDescription": "Wprowadź adres w formacie CIDR (np. 103.21.244.0/22)",
"rulesMatchIpAddress": "Wprowadź adres IP (np. 103.21.244.12)", "rulesMatchIpAddress": "Wprowadź adres IP (np. 103.21.244.12)",
"rulesMatchUrl": "Wprowadź ścieżkę URL lub wzorzec (np. /api/v1/todos lub /api/v1/*)", "rulesMatchUrl": "Wprowadź ścieżkę URL lub wzorzec (np. /api/v1/todos lub /api/v1/*)",
"rulesErrorInvalidPriority": "Invalid priority", "rulesErrorInvalidPriority": "Nieprawidłowy priorytet",
"rulesErrorInvalidPriorityDescription": "Enter a whole number of 1 or higher.", "rulesErrorInvalidPriorityDescription": "Wprowadź prawidłowy priorytet",
"rulesErrorDuplicatePriority": "Duplicate priorities", "rulesErrorDuplicatePriority": "Zduplikowane priorytety",
"rulesErrorDuplicatePriorityDescription": "Each rule must have a unique priority number.", "rulesErrorDuplicatePriorityDescription": "Wprowadź unikalne priorytety",
"rulesErrorValidation": "Invalid rules",
"rulesErrorValidationRuleDescription": "Rule {ruleNumber}: {message}",
"rulesErrorValueRequired": "Enter a value for this rule.",
"rulesErrorInvalidCountry": "Invalid country",
"rulesErrorInvalidCountryDescription": "Select a valid country.",
"rulesErrorInvalidAsn": "Invalid ASN",
"rulesErrorInvalidAsnDescription": "Enter a valid ASN (e.g., AS15169).",
"ruleUpdated": "Reguły zaktualizowane", "ruleUpdated": "Reguły zaktualizowane",
"ruleUpdatedDescription": "Reguły zostały pomyślnie zaktualizowane", "ruleUpdatedDescription": "Reguły zostały pomyślnie zaktualizowane",
"ruleErrorUpdate": "Operacja nie powiodła się", "ruleErrorUpdate": "Operacja nie powiodła się",
"ruleErrorUpdateDescription": "Wystąpił błąd podczas operacji zapisu", "ruleErrorUpdateDescription": "Wystąpił błąd podczas operacji zapisu",
"rulesPriority": "Priorytet", "rulesPriority": "Priorytet",
"rulesReorderDragHandle": "Drag to reorder rule priority",
"rulesAction": "Akcja", "rulesAction": "Akcja",
"rulesMatchType": "Typ dopasowania", "rulesMatchType": "Typ dopasowania",
"value": "Wartość", "value": "Wartość",
@@ -803,7 +792,7 @@
"rulesResource": "Konfiguracja reguł zasobu", "rulesResource": "Konfiguracja reguł zasobu",
"rulesResourceDescription": "Skonfiguruj reguły, aby kontrolować dostęp do zasobu", "rulesResourceDescription": "Skonfiguruj reguły, aby kontrolować dostęp do zasobu",
"ruleSubmit": "Dodaj regułę", "ruleSubmit": "Dodaj regułę",
"rulesNoOne": "No rules yet.", "rulesNoOne": "Brak reguł. Dodaj regułę używając formularza.",
"rulesOrder": "Reguły są oceniane według priorytetu w kolejności rosnącej.", "rulesOrder": "Reguły są oceniane według priorytetu w kolejności rosnącej.",
"rulesSubmit": "Zapisz reguły", "rulesSubmit": "Zapisz reguły",
"policyErrorCreate": "Błąd przy tworzeniu polityki", "policyErrorCreate": "Błąd przy tworzeniu polityki",
@@ -814,44 +803,7 @@
"policyErrorUpdateMessageDescription": "Wystąpił nieoczekiwany błąd", "policyErrorUpdateMessageDescription": "Wystąpił nieoczekiwany błąd",
"policyCreatedSuccess": "Polityka zasobów została pomyślnie utworzona", "policyCreatedSuccess": "Polityka zasobów została pomyślnie utworzona",
"policyUpdatedSuccess": "Polityka zasobów została pomyślnie zaktualizowana", "policyUpdatedSuccess": "Polityka zasobów została pomyślnie zaktualizowana",
"authMethodsSave": "Save Settings", "authMethodsSave": "Zapisz metody uwierzytelniania",
"policyAuthStackTitle": "Authentication",
"policyAuthStackDescription": "Control which authentication methods are required to access this resource",
"policyAuthOrLogicTitle": "Multiple authentication methods active",
"policyAuthOrLogicBanner": "Visitors may authenticate using any one of the active methods below. They do not need to complete all of them.",
"policyAuthMethodActive": "Active",
"policyAuthMethodOff": "Off",
"policyAuthSsoTitle": "Platform SSO",
"policyAuthSsoDescription": "Require sign-in through your organization's identity provider",
"policyAuthSsoSummary": "{idp} · {users} users, {roles} roles",
"policyAuthSsoDefaultIdp": "Default provider",
"policyAuthAddDefaultIdentityProvider": "Add Default Identity Provider",
"policyAuthOtherMethodsTitle": "Other Methods",
"policyAuthOtherMethodsDescription": "Optional methods visitors can use instead of or alongside platform SSO",
"policyAuthPasscodeTitle": "Passcode",
"policyAuthPasscodeDescription": "Require a shared alphanumeric passcode to access the resource",
"policyAuthPasscodeSummary": "Passcode set",
"policyAuthPincodeTitle": "PIN Code",
"policyAuthPincodeDescription": "A short numeric code required to access the resource",
"policyAuthPincodeSummary": "6-digit PIN set",
"policyAuthEmailTitle": "Email Whitelist",
"policyAuthEmailDescription": "Allow listed email addresses with one-time passwords",
"policyAuthEmailSummary": "{count} addresses allowed",
"policyAuthEmailOtpCallout": "Enabling email whitelist sends a one-time password to the visitor's email on login.",
"policyAuthHeaderAuthTitle": "Basic Header Auth",
"policyAuthHeaderAuthDescription": "Validate a custom HTTP header name and value on each request",
"policyAuthHeaderAuthSummary": "Header configured",
"policyAuthHeaderName": "Header name",
"policyAuthHeaderValue": "Expected value",
"policyAccessRulesTitle": "Access Rules",
"policyAccessRulesEnableDescription": "When enabled, rules are evaluated in descending order until one evaluates as true.",
"policyAccessRulesFirstMatch": "Rules are evaluated top to bottom. The first matching rule decides the outcome.",
"policyAccessRulesHowItWorks": "Rules match requests by path, IP address, location, or other criteria. Each rule applies an action: bypass authentication, block access, or pass to authentication. If no rule matches, traffic continues to authentication.",
"policyAccessRulesFallthroughOff": "When rules are disabled, all traffic passes through to authentication.",
"policyAccessRulesFallthroughOn": "When no rule matches, traffic passes through to authentication.",
"rulesPlaceholderCidr": "10.0.0.0/8",
"rulesPlaceholderPath": "/admin/*",
"rulesPlaceholderGeo": "RU, KP",
"rulesSave": "Zapisz zasady", "rulesSave": "Zapisz zasady",
"resourceErrorCreate": "Błąd podczas tworzenia zasobu", "resourceErrorCreate": "Błąd podczas tworzenia zasobu",
"resourceErrorCreateDescription": "Wystąpił błąd podczas tworzenia zasobu", "resourceErrorCreateDescription": "Wystąpił błąd podczas tworzenia zasobu",
@@ -1268,10 +1220,8 @@
"addLabels": "Dodaj etykiety", "addLabels": "Dodaj etykiety",
"siteLabelsTab": "Etykiety", "siteLabelsTab": "Etykiety",
"siteLabelsDescription": "Zarządzaj etykietami powiązanymi z tą stroną.", "siteLabelsDescription": "Zarządzaj etykietami powiązanymi z tą stroną.",
"labelsNotFound": "No labels found.", "labelsNotFound": "Nie znaleziono etykiet",
"labelsEmptyCreateHint": "Start typing above to create a label.",
"labelSearch": "Szukaj etykiet", "labelSearch": "Szukaj etykiet",
"labelSearchOrCreate": "Search or create a label",
"accessLabelFilterCount": "{count, plural, one {# etykieta} few {# etykiety} many {# etykiet} other {# etykiet}}", "accessLabelFilterCount": "{count, plural, one {# etykieta} few {# etykiety} many {# etykiet} other {# etykiet}}",
"labelOverflowCount": "+{count, plural, one {# etykieta} few {# etykiety} many {# etykiet} other {# etykiet}}", "labelOverflowCount": "+{count, plural, one {# etykieta} few {# etykiety} many {# etykiet} other {# etykiet}}",
"accessLabelFilterClear": "Wyczyść filtry etykiet", "accessLabelFilterClear": "Wyczyść filtry etykiet",
@@ -1512,7 +1462,7 @@
"sidebarProxyResources": "Publiczne", "sidebarProxyResources": "Publiczne",
"sidebarClientResources": "Prywatny", "sidebarClientResources": "Prywatny",
"sidebarPolicies": "Polityki", "sidebarPolicies": "Polityki",
"sidebarResourcePolicies": "Public Resources", "sidebarResourcePolicies": "Zasoby",
"sidebarAccessControl": "Kontrola dostępu", "sidebarAccessControl": "Kontrola dostępu",
"sidebarLogsAndAnalytics": "Logi i Analityki", "sidebarLogsAndAnalytics": "Logi i Analityki",
"sidebarTeam": "Drużyna", "sidebarTeam": "Drużyna",
@@ -1520,7 +1470,7 @@
"sidebarAdmin": "Administrator", "sidebarAdmin": "Administrator",
"sidebarInvitations": "Zaproszenia", "sidebarInvitations": "Zaproszenia",
"sidebarRoles": "Role", "sidebarRoles": "Role",
"sidebarShareableLinks": "Share Links", "sidebarShareableLinks": "Linki",
"sidebarApiKeys": "Klucze API", "sidebarApiKeys": "Klucze API",
"sidebarProvisioning": "Dostarczanie", "sidebarProvisioning": "Dostarczanie",
"sidebarSettings": "Ustawienia", "sidebarSettings": "Ustawienia",
@@ -1697,7 +1647,7 @@
"standaloneHcFilterResourceIdFallback": "Zasób {id}", "standaloneHcFilterResourceIdFallback": "Zasób {id}",
"blueprints": "Schematy", "blueprints": "Schematy",
"blueprintsLog": "Dziennik szablonów", "blueprintsLog": "Dziennik szablonów",
"blueprintsDescription": "View past blueprint applications and their results or apply a new blueprint", "blueprintsDescription": "Zobacz wcześniejsze zastosowania szablonów i ich wyniki",
"blueprintAdd": "Dodaj schemat", "blueprintAdd": "Dodaj schemat",
"blueprintGoBack": "Zobacz wszystkie schematy", "blueprintGoBack": "Zobacz wszystkie schematy",
"blueprintCreate": "Utwórz schemat", "blueprintCreate": "Utwórz schemat",
@@ -2077,13 +2027,13 @@
"healthCheckUnknown": "Nieznany", "healthCheckUnknown": "Nieznany",
"healthCheck": "Kontrola Zdrowia", "healthCheck": "Kontrola Zdrowia",
"configureHealthCheck": "Skonfiguruj Kontrolę Zdrowia", "configureHealthCheck": "Skonfiguruj Kontrolę Zdrowia",
"configureHealthCheckDescription": "Set up monitoring for your resource to ensure it is always available", "configureHealthCheckDescription": "Skonfiguruj monitorowanie zdrowia dla {target}",
"enableHealthChecks": "Włącz Kontrole Zdrowia", "enableHealthChecks": "Włącz Kontrole Zdrowia",
"healthCheckDisabledStateDescription": "Gdy wyłączone, strona nie będzie wykonywać kontroli zdrowia, a stan zostanie uznany za nieznany.", "healthCheckDisabledStateDescription": "Gdy wyłączone, strona nie będzie wykonywać kontroli zdrowia, a stan zostanie uznany za nieznany.",
"enableHealthChecksDescription": "Monitoruj zdrowie tego celu. Możesz monitorować inny punkt końcowy niż docelowy w razie potrzeby.", "enableHealthChecksDescription": "Monitoruj zdrowie tego celu. Możesz monitorować inny punkt końcowy niż docelowy w razie potrzeby.",
"healthScheme": "Metoda", "healthScheme": "Metoda",
"healthSelectScheme": "Wybierz metodę", "healthSelectScheme": "Wybierz metodę",
"healthCheckPortInvalid": "Port must be between 1 and 65535", "healthCheckPortInvalid": "Port oceny stanu musi znajdować się między 1 a 65535",
"healthCheckPath": "Ścieżka", "healthCheckPath": "Ścieżka",
"healthHostname": "IP / Nazwa hosta", "healthHostname": "IP / Nazwa hosta",
"healthPort": "Port", "healthPort": "Port",
@@ -2096,7 +2046,6 @@
"requireDeviceApproval": "Wymagaj zatwierdzenia urządzenia", "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.", "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.",
"sshSettings": "Ustawienia SSH", "sshSettings": "Ustawienia SSH",
"sshAccess": "SSH Access",
"rdpSettings": "Ustawienia RDP", "rdpSettings": "Ustawienia RDP",
"vncSettings": "Ustawienia VNC", "vncSettings": "Ustawienia VNC",
"sshServer": "Serwer SSH", "sshServer": "Serwer SSH",
@@ -2123,13 +2072,8 @@
"sshDaemonDisclaimer": "Upewnij się, że Twoja maszyna docelowa jest poprawnie skonfigurowana do uruchamiania demona uwierzytelniania zanim ukończysz tę konfigurację, w przeciwnym razie provisioning zakończy się niepowodzeniem.", "sshDaemonDisclaimer": "Upewnij się, że Twoja maszyna docelowa jest poprawnie skonfigurowana do uruchamiania demona uwierzytelniania zanim ukończysz tę konfigurację, w przeciwnym razie provisioning zakończy się niepowodzeniem.",
"sshDaemonPort": "Port Demona", "sshDaemonPort": "Port Demona",
"sshServerDestination": "Miejsce docelowe serwera", "sshServerDestination": "Miejsce docelowe serwera",
"sshServerDestinationDescription": "Configure the destination of the SSH server", "sshServerDestinationDescription": "Skonfiguruj miejsce docelowe i port serwera SSH",
"destination": "Miejsce docelowe", "destination": "Miejsce docelowe",
"destinationRequired": "Destination is required.",
"domainRequired": "Domain is required.",
"proxyPortRequired": "Port is required.",
"invalidPathConfiguration": "Invalid path configuration.",
"invalidRewritePathConfiguration": "Invalid rewrite path configuration.",
"bgTargetMultiSiteDisclaimer": "Wybór wielu stron umożliwia odporność trasowania i zmienioność dla wysokiej dostępności.", "bgTargetMultiSiteDisclaimer": "Wybór wielu stron umożliwia odporność trasowania i zmienioność dla wysokiej dostępności.",
"roleAllowSsh": "Zezwalaj na SSH", "roleAllowSsh": "Zezwalaj na SSH",
"roleAllowSshAllow": "Zezwól", "roleAllowSshAllow": "Zezwól",
@@ -3090,7 +3034,7 @@
"enterConfirmation": "Wprowadź potwierdzenie", "enterConfirmation": "Wprowadź potwierdzenie",
"blueprintViewDetails": "Szczegóły", "blueprintViewDetails": "Szczegóły",
"defaultIdentityProvider": "Domyślny dostawca tożsamości", "defaultIdentityProvider": "Domyślny dostawca tożsamości",
"defaultIdentityProviderDescription": "The user will be automatically redirected to this identity provider for authentication.", "defaultIdentityProviderDescription": "Gdy zostanie wybrany domyślny dostawca tożsamości, użytkownik zostanie automatycznie przekierowany do dostawcy w celu uwierzytelnienia.",
"editInternalResourceDialogNetworkSettings": "Ustawienia sieci", "editInternalResourceDialogNetworkSettings": "Ustawienia sieci",
"editInternalResourceDialogAccessPolicy": "Polityka dostępowa", "editInternalResourceDialogAccessPolicy": "Polityka dostępowa",
"editInternalResourceDialogAddRoles": "Dodaj role", "editInternalResourceDialogAddRoles": "Dodaj role",
@@ -3402,8 +3346,6 @@
"idpUnassociateQuestion": "Czy na pewno chcesz odłączyć tego dostawcę tożsamości od tej organizacji?", "idpUnassociateQuestion": "Czy na pewno chcesz odłączyć tego dostawcę tożsamości od tej organizacji?",
"idpUnassociateDescription": "Wszystkie użytkownicy powiązani z tym dostawcą tożsamości zostaną usunięci z tej organizacji, ale dostawca tożsamości będzie nadal istniał dla innych powiązanych organizacji.", "idpUnassociateDescription": "Wszystkie użytkownicy powiązani z tym dostawcą tożsamości zostaną usunięci z tej organizacji, ale dostawca tożsamości będzie nadal istniał dla innych powiązanych organizacji.",
"idpUnassociateConfirm": "Potwierdź odłączenie dostawcy tożsamości", "idpUnassociateConfirm": "Potwierdź odłączenie dostawcy tożsamości",
"idpConfirmDeleteAndRemoveMeFromOrg": "DELETE AND REMOVE ME FROM ORG",
"idpUnassociateAndRemoveMeFromOrg": "UNASSOCIATE AND REMOVE ME FROM ORG",
"idpUnassociateWarning": "Tego nie można cofnąć dla tej organizacji.", "idpUnassociateWarning": "Tego nie można cofnąć dla tej organizacji.",
"idpUnassociatedDescription": "Dostawca tożsamości pomyślnie odłączony od tej organizacji", "idpUnassociatedDescription": "Dostawca tożsamości pomyślnie odłączony od tej organizacji",
"idpUnassociateMenu": "Odłącz", "idpUnassociateMenu": "Odłącz",
@@ -3510,43 +3452,5 @@
"sshErrorNoTarget": "Nie określono celu", "sshErrorNoTarget": "Nie określono celu",
"sshErrorWebSocket": "Połączenie WebSocket nie powiodło się", "sshErrorWebSocket": "Połączenie WebSocket nie powiodło się",
"sshErrorAuthFailed": "Uwierzytelnianie nie powiodło się", "sshErrorAuthFailed": "Uwierzytelnianie nie powiodło się",
"sshErrorConnectionClosed": "Połączenie zamknięte przed ukończeniem uwierzytelniania", "sshErrorConnectionClosed": "Połączenie zamknięte przed ukończeniem uwierzytelniania"
"sitePangolinSshDescription": "Allow SSH access to resources on this site. This can be changed later.",
"browserGatewayNoResourceForDomain": "No resource found for this domain",
"browserGatewayNoTarget": "No target",
"browserGatewayConnect": "Connect",
"browserGatewayCtrlAltDel": "Ctrl+Alt+Del",
"sshErrorSignKeyFailed": "Failed to sign SSH key for PAM push authentication. Did you sign in as a user?",
"sshTerminalError": "Error: {error}",
"sshConnectionClosedCode": "Connection closed (code {code})",
"sshPrivateKeyPlaceholder": "-----BEGIN OPENSSH PRIVATE KEY-----",
"sshPrivateKeyRequired": "Private key is required",
"vncTitle": "VNC",
"vncSignInDescription": "Enter your VNC password to connect",
"vncPasswordOptional": "Password (optional)",
"vncNoResourceTarget": "No resource target is available",
"vncFailedToLoadNovnc": "Failed to load noVNC",
"vncAuthFailedStatus": "Status {status}",
"vncPasteClipboard": "Paste clipboard",
"rdpTitle": "RDP",
"rdpSignInTitle": "Sign in to Remote Desktop",
"rdpSignInDescription": "Enter Windows credentials to connect",
"rdpLoadingModule": "Loading module...",
"rdpFailedToLoadModule": "Failed to load RDP module",
"rdpNotReady": "Not ready",
"rdpModuleInitializing": "RDP module is still initializing",
"rdpDownloadingFiles": "Downloading {count} file(s) from remote…",
"rdpDownloadFailed": "Download failed: {fileName}",
"rdpUploaded": "Uploaded: {fileName}",
"rdpNoConnectionTarget": "No connection target available",
"rdpConnectionFailed": "Connection failed",
"rdpFit": "Fit",
"rdpFull": "Full",
"rdpReal": "Real",
"rdpMeta": "Meta",
"rdpUploadFiles": "Upload files",
"rdpFilesReadyToPaste": "Files ready to paste",
"rdpFilesReadyToPasteDescription": "{count} file(s) copied to remote clipboard — press Ctrl+V on the remote desktop to paste.",
"rdpUploadFailed": "Upload failed",
"rdpUnicodeKeyboardMode": "Unicode keyboard mode"
} }
+27 -123
View File
@@ -101,8 +101,6 @@
"sitesTableViewPrivateResources": "Visualizar Recursos Privados", "sitesTableViewPrivateResources": "Visualizar Recursos Privados",
"siteInstallNewt": "Instalar Novo", "siteInstallNewt": "Instalar Novo",
"siteInstallNewtDescription": "Novo item em execução no seu sistema", "siteInstallNewtDescription": "Novo item em execução no seu sistema",
"siteInstallKubernetesDocsDescription": "For more and up to date Kubernetes installation information, see <docsLink>docs.pangolin.net/manage/sites/install-kubernetes</docsLink>.",
"siteInstallAdvantechDocsDescription": "For Advantech modem installation instructions, see <docsLink>docs.pangolin.net/manage/sites/install-advantech</docsLink>.",
"WgConfiguration": "Configuração do WireGuard", "WgConfiguration": "Configuração do WireGuard",
"WgConfigurationDescription": "Use a seguinte configuração para conectar-se à rede", "WgConfigurationDescription": "Use a seguinte configuração para conectar-se à rede",
"operatingSystem": "Sistema operacional", "operatingSystem": "Sistema operacional",
@@ -202,8 +200,8 @@
"shareErrorSelectResource": "Por favor, selecione um recurso", "shareErrorSelectResource": "Por favor, selecione um recurso",
"proxyResourceTitle": "Gerenciar Recursos Públicos", "proxyResourceTitle": "Gerenciar Recursos Públicos",
"proxyResourceDescription": "Criar e gerenciar recursos que são acessíveis publicamente por meio de um navegador da web", "proxyResourceDescription": "Criar e gerenciar recursos que são acessíveis publicamente por meio de um navegador da web",
"publicResourcesBannerTitle": "Web-based Public Access", "publicResourcesBannerTitle": "Acesso Público via Web",
"publicResourcesBannerDescription": "Public resources are HTTPS proxies accessible to anyone on the internet through a web browser. Unlike private resources, they do not require client-side software and can include identity and context-aware access policies.", "publicResourcesBannerDescription": "Os recursos públicos são proxies HTTPS ou TCP/UDP acessíveis a qualquer pessoa na internet por meio de um navegador web. Ao contrário dos recursos privados, eles não requerem software do lado do cliente e podem incluir políticas de acesso conscientes de identidade e contexto.",
"clientResourceTitle": "Gerenciar recursos privados", "clientResourceTitle": "Gerenciar recursos privados",
"clientResourceDescription": "Criar e gerenciar recursos que só são acessíveis por meio de um cliente conectado", "clientResourceDescription": "Criar e gerenciar recursos que só são acessíveis por meio de um cliente conectado",
"privateResourcesBannerTitle": "Acesso Privado com Confiança Zero", "privateResourcesBannerTitle": "Acesso Privado com Confiança Zero",
@@ -211,16 +209,15 @@
"resourcesSearch": "Procurar recursos...", "resourcesSearch": "Procurar recursos...",
"resourceAdd": "Adicionar Recurso", "resourceAdd": "Adicionar Recurso",
"resourceErrorDelte": "Erro ao apagar recurso", "resourceErrorDelte": "Erro ao apagar recurso",
"resourcePoliciesTitle": "Manage Public Resource Policies", "resourcePoliciesTitle": "Gerenciar Políticas de Recurso",
"resourcePoliciesAttachedResourcesColumnTitle": "Resources", "resourcePoliciesAttachedResourcesColumnTitle": "Recursos Anexados",
"resourcePoliciesAttachedResources": "{count} recurso(s)", "resourcePoliciesAttachedResources": "{count} recurso(s)",
"resourcePoliciesAttachedResourcesCount": "{count, plural, one {# resource} other {# resources}}",
"resourcePoliciesAttachedResourcesEmpty": "sem recursos", "resourcePoliciesAttachedResourcesEmpty": "sem recursos",
"resourcePoliciesDescription": "Create and manage authentication policies to control access to your public resources", "resourcePoliciesDescription": "Crie e gerencie políticas de autenticação para controlar o acesso aos seus recursos",
"resourcePoliciesSearch": "Pesquisar políticas...", "resourcePoliciesSearch": "Pesquisar políticas...",
"resourcePoliciesAdd": "Adicionar Política", "resourcePoliciesAdd": "Adicionar Política",
"resourcePoliciesDefaultBadgeText": "Política Padrão", "resourcePoliciesDefaultBadgeText": "Política Padrão",
"resourcePoliciesCreate": "Create Public Resource Policy", "resourcePoliciesCreate": "Criar Política de Recurso",
"resourcePoliciesCreateDescription": "Siga os passos abaixo para criar uma nova política", "resourcePoliciesCreateDescription": "Siga os passos abaixo para criar uma nova política",
"resourcePolicyName": "Nome da Política", "resourcePolicyName": "Nome da Política",
"resourcePolicyNameDescription": "Dê um nome a esta política para identificá-la em seus recursos", "resourcePolicyNameDescription": "Dê um nome a esta política para identificá-la em seus recursos",
@@ -314,7 +311,7 @@
"rules": "Regras", "rules": "Regras",
"resourceSettingDescription": "Configure as configurações do recurso", "resourceSettingDescription": "Configure as configurações do recurso",
"resourceSetting": "Configurações do {resourceName}", "resourceSetting": "Configurações do {resourceName}",
"resourcePolicySettingDescription": "Configure the settings on this public resource policy", "resourcePolicySettingDescription": "Configure as configurações na política de recurso",
"resourcePolicySetting": "Configurações de {policyName}", "resourcePolicySetting": "Configurações de {policyName}",
"alwaysAllow": "Autenticação de bypass", "alwaysAllow": "Autenticação de bypass",
"alwaysDeny": "Bloquear Acesso", "alwaysDeny": "Bloquear Acesso",
@@ -756,11 +753,11 @@
"rulesErrorDuplicate": "Regra duplicada", "rulesErrorDuplicate": "Regra duplicada",
"rulesErrorDuplicateDescription": "Uma regra com estas configurações já existe", "rulesErrorDuplicateDescription": "Uma regra com estas configurações já existe",
"rulesErrorInvalidIpAddressRange": "CIDR inválido", "rulesErrorInvalidIpAddressRange": "CIDR inválido",
"rulesErrorInvalidIpAddressRangeDescription": "Enter a valid CIDR range (e.g., 10.0.0.0/8).", "rulesErrorInvalidIpAddressRangeDescription": "Por favor, insira um valor CIDR válido",
"rulesErrorInvalidUrl": "Invalid path", "rulesErrorInvalidUrl": "Caminho URL inválido",
"rulesErrorInvalidUrlDescription": "Enter a valid URL path or pattern (e.g., /api/*).", "rulesErrorInvalidUrlDescription": "Por favor, insira um valor de caminho URL válido",
"rulesErrorInvalidIpAddress": "Invalid IP address", "rulesErrorInvalidIpAddress": "IP inválido",
"rulesErrorInvalidIpAddressDescription": "Enter a valid IPv4 or IPv6 address.", "rulesErrorInvalidIpAddressDescription": "Por favor, insira um endereço IP válido",
"rulesErrorUpdate": "Falha ao atualizar regras", "rulesErrorUpdate": "Falha ao atualizar regras",
"rulesErrorUpdateDescription": "Ocorreu um erro ao atualizar regras", "rulesErrorUpdateDescription": "Ocorreu um erro ao atualizar regras",
"rulesUpdated": "Ativar Regras", "rulesUpdated": "Ativar Regras",
@@ -768,23 +765,15 @@
"rulesMatchIpAddressRangeDescription": "Insira um endereço no formato CIDR (ex: 103.21.244.0/22)", "rulesMatchIpAddressRangeDescription": "Insira um endereço no formato CIDR (ex: 103.21.244.0/22)",
"rulesMatchIpAddress": "Insira um endereço IP (ex: 103.21.244.12)", "rulesMatchIpAddress": "Insira um endereço IP (ex: 103.21.244.12)",
"rulesMatchUrl": "Insira um caminho URL ou padrão (ex: /api/v1/todos ou /api/v1/*)", "rulesMatchUrl": "Insira um caminho URL ou padrão (ex: /api/v1/todos ou /api/v1/*)",
"rulesErrorInvalidPriority": "Invalid priority", "rulesErrorInvalidPriority": "Prioridade Inválida",
"rulesErrorInvalidPriorityDescription": "Enter a whole number of 1 or higher.", "rulesErrorInvalidPriorityDescription": "Por favor, insira uma prioridade válida",
"rulesErrorDuplicatePriority": "Duplicate priorities", "rulesErrorDuplicatePriority": "Prioridades Duplicadas",
"rulesErrorDuplicatePriorityDescription": "Each rule must have a unique priority number.", "rulesErrorDuplicatePriorityDescription": "Por favor, insira prioridades únicas",
"rulesErrorValidation": "Invalid rules",
"rulesErrorValidationRuleDescription": "Rule {ruleNumber}: {message}",
"rulesErrorValueRequired": "Enter a value for this rule.",
"rulesErrorInvalidCountry": "Invalid country",
"rulesErrorInvalidCountryDescription": "Select a valid country.",
"rulesErrorInvalidAsn": "Invalid ASN",
"rulesErrorInvalidAsnDescription": "Enter a valid ASN (e.g., AS15169).",
"ruleUpdated": "Regras atualizadas", "ruleUpdated": "Regras atualizadas",
"ruleUpdatedDescription": "Regras atualizadas com sucesso", "ruleUpdatedDescription": "Regras atualizadas com sucesso",
"ruleErrorUpdate": "Operação falhou", "ruleErrorUpdate": "Operação falhou",
"ruleErrorUpdateDescription": "Ocorreu um erro durante a operação de salvamento", "ruleErrorUpdateDescription": "Ocorreu um erro durante a operação de salvamento",
"rulesPriority": "Prioridade", "rulesPriority": "Prioridade",
"rulesReorderDragHandle": "Drag to reorder rule priority",
"rulesAction": "Ação", "rulesAction": "Ação",
"rulesMatchType": "Tipo de Correspondência", "rulesMatchType": "Tipo de Correspondência",
"value": "Valor", "value": "Valor",
@@ -803,7 +792,7 @@
"rulesResource": "Configuração de Regras do Recurso", "rulesResource": "Configuração de Regras do Recurso",
"rulesResourceDescription": "Configurar regras para controlar o acesso ao recurso", "rulesResourceDescription": "Configurar regras para controlar o acesso ao recurso",
"ruleSubmit": "Adicionar Regra", "ruleSubmit": "Adicionar Regra",
"rulesNoOne": "No rules yet.", "rulesNoOne": "Sem regras. Adicione uma regra usando o formulário.",
"rulesOrder": "As regras são avaliadas por prioridade em ordem ascendente.", "rulesOrder": "As regras são avaliadas por prioridade em ordem ascendente.",
"rulesSubmit": "Guardar Regras", "rulesSubmit": "Guardar Regras",
"policyErrorCreate": "Erro ao criar política", "policyErrorCreate": "Erro ao criar política",
@@ -814,44 +803,7 @@
"policyErrorUpdateMessageDescription": "Ocorreu um erro inesperado", "policyErrorUpdateMessageDescription": "Ocorreu um erro inesperado",
"policyCreatedSuccess": "Política de recurso criada com sucesso", "policyCreatedSuccess": "Política de recurso criada com sucesso",
"policyUpdatedSuccess": "Política de recurso atualizada com sucesso", "policyUpdatedSuccess": "Política de recurso atualizada com sucesso",
"authMethodsSave": "Save Settings", "authMethodsSave": "Salvar métodos de autenticação",
"policyAuthStackTitle": "Authentication",
"policyAuthStackDescription": "Control which authentication methods are required to access this resource",
"policyAuthOrLogicTitle": "Multiple authentication methods active",
"policyAuthOrLogicBanner": "Visitors may authenticate using any one of the active methods below. They do not need to complete all of them.",
"policyAuthMethodActive": "Active",
"policyAuthMethodOff": "Off",
"policyAuthSsoTitle": "Platform SSO",
"policyAuthSsoDescription": "Require sign-in through your organization's identity provider",
"policyAuthSsoSummary": "{idp} · {users} users, {roles} roles",
"policyAuthSsoDefaultIdp": "Default provider",
"policyAuthAddDefaultIdentityProvider": "Add Default Identity Provider",
"policyAuthOtherMethodsTitle": "Other Methods",
"policyAuthOtherMethodsDescription": "Optional methods visitors can use instead of or alongside platform SSO",
"policyAuthPasscodeTitle": "Passcode",
"policyAuthPasscodeDescription": "Require a shared alphanumeric passcode to access the resource",
"policyAuthPasscodeSummary": "Passcode set",
"policyAuthPincodeTitle": "PIN Code",
"policyAuthPincodeDescription": "A short numeric code required to access the resource",
"policyAuthPincodeSummary": "6-digit PIN set",
"policyAuthEmailTitle": "Email Whitelist",
"policyAuthEmailDescription": "Allow listed email addresses with one-time passwords",
"policyAuthEmailSummary": "{count} addresses allowed",
"policyAuthEmailOtpCallout": "Enabling email whitelist sends a one-time password to the visitor's email on login.",
"policyAuthHeaderAuthTitle": "Basic Header Auth",
"policyAuthHeaderAuthDescription": "Validate a custom HTTP header name and value on each request",
"policyAuthHeaderAuthSummary": "Header configured",
"policyAuthHeaderName": "Header name",
"policyAuthHeaderValue": "Expected value",
"policyAccessRulesTitle": "Access Rules",
"policyAccessRulesEnableDescription": "When enabled, rules are evaluated in descending order until one evaluates as true.",
"policyAccessRulesFirstMatch": "Rules are evaluated top to bottom. The first matching rule decides the outcome.",
"policyAccessRulesHowItWorks": "Rules match requests by path, IP address, location, or other criteria. Each rule applies an action: bypass authentication, block access, or pass to authentication. If no rule matches, traffic continues to authentication.",
"policyAccessRulesFallthroughOff": "When rules are disabled, all traffic passes through to authentication.",
"policyAccessRulesFallthroughOn": "When no rule matches, traffic passes through to authentication.",
"rulesPlaceholderCidr": "10.0.0.0/8",
"rulesPlaceholderPath": "/admin/*",
"rulesPlaceholderGeo": "RU, KP",
"rulesSave": "Guardar Regras", "rulesSave": "Guardar Regras",
"resourceErrorCreate": "Erro ao criar recurso", "resourceErrorCreate": "Erro ao criar recurso",
"resourceErrorCreateDescription": "Ocorreu um erro ao criar o recurso", "resourceErrorCreateDescription": "Ocorreu um erro ao criar o recurso",
@@ -1268,10 +1220,8 @@
"addLabels": "Adicionar etiquetas", "addLabels": "Adicionar etiquetas",
"siteLabelsTab": "Etiquetas", "siteLabelsTab": "Etiquetas",
"siteLabelsDescription": "Gerencie etiquetas associadas a este site.", "siteLabelsDescription": "Gerencie etiquetas associadas a este site.",
"labelsNotFound": "No labels found.", "labelsNotFound": "Etiquetas não encontradas",
"labelsEmptyCreateHint": "Start typing above to create a label.",
"labelSearch": "Pesquisar etiquetas", "labelSearch": "Pesquisar etiquetas",
"labelSearchOrCreate": "Search or create a label",
"accessLabelFilterCount": "{count, plural, one {# etiqueta} other {# etiquetas}}", "accessLabelFilterCount": "{count, plural, one {# etiqueta} other {# etiquetas}}",
"labelOverflowCount": "+{count, plural, one {# etiqueta} other {# etiquetas}}", "labelOverflowCount": "+{count, plural, one {# etiqueta} other {# etiquetas}}",
"accessLabelFilterClear": "Limpar filtros de etiquetas", "accessLabelFilterClear": "Limpar filtros de etiquetas",
@@ -1512,7 +1462,7 @@
"sidebarProxyResources": "Público", "sidebarProxyResources": "Público",
"sidebarClientResources": "Privado", "sidebarClientResources": "Privado",
"sidebarPolicies": "Políticas", "sidebarPolicies": "Políticas",
"sidebarResourcePolicies": "Public Resources", "sidebarResourcePolicies": "Recursos",
"sidebarAccessControl": "Controle de Acesso", "sidebarAccessControl": "Controle de Acesso",
"sidebarLogsAndAnalytics": "Registros e Análises", "sidebarLogsAndAnalytics": "Registros e Análises",
"sidebarTeam": "Equipe", "sidebarTeam": "Equipe",
@@ -1520,7 +1470,7 @@
"sidebarAdmin": "Administrador", "sidebarAdmin": "Administrador",
"sidebarInvitations": "Convites", "sidebarInvitations": "Convites",
"sidebarRoles": "Papéis", "sidebarRoles": "Papéis",
"sidebarShareableLinks": "Share Links", "sidebarShareableLinks": "Links",
"sidebarApiKeys": "Chaves API", "sidebarApiKeys": "Chaves API",
"sidebarProvisioning": "Provisionamento", "sidebarProvisioning": "Provisionamento",
"sidebarSettings": "Configurações", "sidebarSettings": "Configurações",
@@ -1697,7 +1647,7 @@
"standaloneHcFilterResourceIdFallback": "Recurso {id}", "standaloneHcFilterResourceIdFallback": "Recurso {id}",
"blueprints": "Diagramas", "blueprints": "Diagramas",
"blueprintsLog": "Registo dos Blueprint", "blueprintsLog": "Registo dos Blueprint",
"blueprintsDescription": "View past blueprint applications and their results or apply a new blueprint", "blueprintsDescription": "Ver aplicações de blueprint passadas e seus resultados",
"blueprintAdd": "Adicionar Diagrama", "blueprintAdd": "Adicionar Diagrama",
"blueprintGoBack": "Ver todos os Diagramas", "blueprintGoBack": "Ver todos os Diagramas",
"blueprintCreate": "Criar Diagrama", "blueprintCreate": "Criar Diagrama",
@@ -2077,13 +2027,13 @@
"healthCheckUnknown": "Desconhecido", "healthCheckUnknown": "Desconhecido",
"healthCheck": "Verificação de Saúde", "healthCheck": "Verificação de Saúde",
"configureHealthCheck": "Configurar Verificação de Saúde", "configureHealthCheck": "Configurar Verificação de Saúde",
"configureHealthCheckDescription": "Set up monitoring for your resource to ensure it is always available", "configureHealthCheckDescription": "Configure a monitorização de saúde para {target}",
"enableHealthChecks": "Ativar Verificações de Saúde", "enableHealthChecks": "Ativar Verificações de Saúde",
"healthCheckDisabledStateDescription": "Quando desativado, o site não realizará verificações de saúde e o estado será considerado desconhecido.", "healthCheckDisabledStateDescription": "Quando desativado, o site não realizará verificações de saúde e o estado será considerado desconhecido.",
"enableHealthChecksDescription": "Monitore a saúde deste alvo. Você pode monitorar um ponto de extremidade diferente do alvo, se necessário.", "enableHealthChecksDescription": "Monitore a saúde deste alvo. Você pode monitorar um ponto de extremidade diferente do alvo, se necessário.",
"healthScheme": "Método", "healthScheme": "Método",
"healthSelectScheme": "Selecione o Método", "healthSelectScheme": "Selecione o Método",
"healthCheckPortInvalid": "Port must be between 1 and 65535", "healthCheckPortInvalid": "A porta do exame de saúde deve estar entre 1 e 65535",
"healthCheckPath": "Caminho", "healthCheckPath": "Caminho",
"healthHostname": "IP / Nome do Host", "healthHostname": "IP / Nome do Host",
"healthPort": "Porta", "healthPort": "Porta",
@@ -2096,7 +2046,6 @@
"requireDeviceApproval": "Exigir aprovação do dispositivo", "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.", "requireDeviceApprovalDescription": "Usuários com esta função precisam de novos dispositivos aprovados por um administrador antes que eles possam se conectar e acessar recursos.",
"sshSettings": "Configurações SSH", "sshSettings": "Configurações SSH",
"sshAccess": "SSH Access",
"rdpSettings": "Configurações RDP", "rdpSettings": "Configurações RDP",
"vncSettings": "Configurações VNC", "vncSettings": "Configurações VNC",
"sshServer": "Servidor SSH", "sshServer": "Servidor SSH",
@@ -2123,13 +2072,8 @@
"sshDaemonDisclaimer": "Certifique-se de que seu host de destino está devidamente configurado para executar o daemon de autenticação antes de concluir esta configuração, ou o provisionamento falhará.", "sshDaemonDisclaimer": "Certifique-se de que seu host de destino está devidamente configurado para executar o daemon de autenticação antes de concluir esta configuração, ou o provisionamento falhará.",
"sshDaemonPort": "Porta do Daemon", "sshDaemonPort": "Porta do Daemon",
"sshServerDestination": "Destino do Servidor", "sshServerDestination": "Destino do Servidor",
"sshServerDestinationDescription": "Configure the destination of the SSH server", "sshServerDestinationDescription": "Configure o destino e a porta do servidor SSH",
"destination": "Destino", "destination": "Destino",
"destinationRequired": "Destination is required.",
"domainRequired": "Domain is required.",
"proxyPortRequired": "Port is required.",
"invalidPathConfiguration": "Invalid path configuration.",
"invalidRewritePathConfiguration": "Invalid rewrite path configuration.",
"bgTargetMultiSiteDisclaimer": "Selecionar vários sites permite roteamento resiliente e failover para alta disponibilidade.", "bgTargetMultiSiteDisclaimer": "Selecionar vários sites permite roteamento resiliente e failover para alta disponibilidade.",
"roleAllowSsh": "Permitir SSH", "roleAllowSsh": "Permitir SSH",
"roleAllowSshAllow": "Autorizar", "roleAllowSshAllow": "Autorizar",
@@ -3090,7 +3034,7 @@
"enterConfirmation": "Inserir confirmação", "enterConfirmation": "Inserir confirmação",
"blueprintViewDetails": "Detalhes", "blueprintViewDetails": "Detalhes",
"defaultIdentityProvider": "Provedor de Identidade Padrão", "defaultIdentityProvider": "Provedor de Identidade Padrão",
"defaultIdentityProviderDescription": "The user will be automatically redirected to this identity provider for authentication.", "defaultIdentityProviderDescription": "Quando um provedor de identidade padrão for selecionado, o usuário será automaticamente redirecionado para o provedor de autenticação.",
"editInternalResourceDialogNetworkSettings": "Configurações de Rede", "editInternalResourceDialogNetworkSettings": "Configurações de Rede",
"editInternalResourceDialogAccessPolicy": "Política de Acesso", "editInternalResourceDialogAccessPolicy": "Política de Acesso",
"editInternalResourceDialogAddRoles": "Adicionar Funções", "editInternalResourceDialogAddRoles": "Adicionar Funções",
@@ -3402,8 +3346,6 @@
"idpUnassociateQuestion": "Tem certeza de que deseja desassociar este provedor de identidade desta organização?", "idpUnassociateQuestion": "Tem certeza de que deseja desassociar este provedor de identidade desta organização?",
"idpUnassociateDescription": "Todos os usuários associados a este provedor de identidade serão removidos desta organização, mas o provedor de identidade continuará a existir para outras organizações associadas.", "idpUnassociateDescription": "Todos os usuários associados a este provedor de identidade serão removidos desta organização, mas o provedor de identidade continuará a existir para outras organizações associadas.",
"idpUnassociateConfirm": "Confirmar Desassociação do Provedor de Identidade", "idpUnassociateConfirm": "Confirmar Desassociação do Provedor de Identidade",
"idpConfirmDeleteAndRemoveMeFromOrg": "DELETE AND REMOVE ME FROM ORG",
"idpUnassociateAndRemoveMeFromOrg": "UNASSOCIATE AND REMOVE ME FROM ORG",
"idpUnassociateWarning": "Isso não pode ser desfeito para esta organização.", "idpUnassociateWarning": "Isso não pode ser desfeito para esta organização.",
"idpUnassociatedDescription": "Provedor de identidade desassociado desta organização com sucesso", "idpUnassociatedDescription": "Provedor de identidade desassociado desta organização com sucesso",
"idpUnassociateMenu": "Desassociar", "idpUnassociateMenu": "Desassociar",
@@ -3510,43 +3452,5 @@
"sshErrorNoTarget": "Nenhum alvo especificado", "sshErrorNoTarget": "Nenhum alvo especificado",
"sshErrorWebSocket": "Falha na conexão WebSocket", "sshErrorWebSocket": "Falha na conexão WebSocket",
"sshErrorAuthFailed": "Falha na autenticação", "sshErrorAuthFailed": "Falha na autenticação",
"sshErrorConnectionClosed": "Conexão encerrada antes de concluir a autenticação", "sshErrorConnectionClosed": "Conexão encerrada antes de concluir a autenticação"
"sitePangolinSshDescription": "Allow SSH access to resources on this site. This can be changed later.",
"browserGatewayNoResourceForDomain": "No resource found for this domain",
"browserGatewayNoTarget": "No target",
"browserGatewayConnect": "Connect",
"browserGatewayCtrlAltDel": "Ctrl+Alt+Del",
"sshErrorSignKeyFailed": "Failed to sign SSH key for PAM push authentication. Did you sign in as a user?",
"sshTerminalError": "Error: {error}",
"sshConnectionClosedCode": "Connection closed (code {code})",
"sshPrivateKeyPlaceholder": "-----BEGIN OPENSSH PRIVATE KEY-----",
"sshPrivateKeyRequired": "Private key is required",
"vncTitle": "VNC",
"vncSignInDescription": "Enter your VNC password to connect",
"vncPasswordOptional": "Password (optional)",
"vncNoResourceTarget": "No resource target is available",
"vncFailedToLoadNovnc": "Failed to load noVNC",
"vncAuthFailedStatus": "Status {status}",
"vncPasteClipboard": "Paste clipboard",
"rdpTitle": "RDP",
"rdpSignInTitle": "Sign in to Remote Desktop",
"rdpSignInDescription": "Enter Windows credentials to connect",
"rdpLoadingModule": "Loading module...",
"rdpFailedToLoadModule": "Failed to load RDP module",
"rdpNotReady": "Not ready",
"rdpModuleInitializing": "RDP module is still initializing",
"rdpDownloadingFiles": "Downloading {count} file(s) from remote…",
"rdpDownloadFailed": "Download failed: {fileName}",
"rdpUploaded": "Uploaded: {fileName}",
"rdpNoConnectionTarget": "No connection target available",
"rdpConnectionFailed": "Connection failed",
"rdpFit": "Fit",
"rdpFull": "Full",
"rdpReal": "Real",
"rdpMeta": "Meta",
"rdpUploadFiles": "Upload files",
"rdpFilesReadyToPaste": "Files ready to paste",
"rdpFilesReadyToPasteDescription": "{count} file(s) copied to remote clipboard — press Ctrl+V on the remote desktop to paste.",
"rdpUploadFailed": "Upload failed",
"rdpUnicodeKeyboardMode": "Unicode keyboard mode"
} }
+27 -123
View File
@@ -101,8 +101,6 @@
"sitesTableViewPrivateResources": "Просмотр частных ресурсов", "sitesTableViewPrivateResources": "Просмотр частных ресурсов",
"siteInstallNewt": "Установить Newt", "siteInstallNewt": "Установить Newt",
"siteInstallNewtDescription": "Запустите Newt в вашей системе", "siteInstallNewtDescription": "Запустите Newt в вашей системе",
"siteInstallKubernetesDocsDescription": "For more and up to date Kubernetes installation information, see <docsLink>docs.pangolin.net/manage/sites/install-kubernetes</docsLink>.",
"siteInstallAdvantechDocsDescription": "For Advantech modem installation instructions, see <docsLink>docs.pangolin.net/manage/sites/install-advantech</docsLink>.",
"WgConfiguration": "Конфигурация WireGuard", "WgConfiguration": "Конфигурация WireGuard",
"WgConfigurationDescription": "Используйте следующую конфигурацию для подключения к сети", "WgConfigurationDescription": "Используйте следующую конфигурацию для подключения к сети",
"operatingSystem": "Операционная система", "operatingSystem": "Операционная система",
@@ -202,8 +200,8 @@
"shareErrorSelectResource": "Пожалуйста, выберите ресурс", "shareErrorSelectResource": "Пожалуйста, выберите ресурс",
"proxyResourceTitle": "Управление публичными ресурсами", "proxyResourceTitle": "Управление публичными ресурсами",
"proxyResourceDescription": "Создание и управление ресурсами, которые доступны через веб-браузер", "proxyResourceDescription": "Создание и управление ресурсами, которые доступны через веб-браузер",
"publicResourcesBannerTitle": "Web-based Public Access", "publicResourcesBannerTitle": "Общедоступный доступ через веб",
"publicResourcesBannerDescription": "Public resources are HTTPS proxies accessible to anyone on the internet through a web browser. Unlike private resources, they do not require client-side software and can include identity and context-aware access policies.", "publicResourcesBannerDescription": "Общедоступные ресурсы - это прокси-по HTTPS или TCP/UDP, доступные любому пользователю в Интернете через веб-браузер. В отличие от частных ресурсов, они не требуют программного обеспечения на стороне клиента и могут включать политики доступа на основе идентификации и контекста.",
"clientResourceTitle": "Управление приватными ресурсами", "clientResourceTitle": "Управление приватными ресурсами",
"clientResourceDescription": "Создание и управление ресурсами, которые доступны только через подключенный клиент", "clientResourceDescription": "Создание и управление ресурсами, которые доступны только через подключенный клиент",
"privateResourcesBannerTitle": "Частный доступ с нулевым доверием", "privateResourcesBannerTitle": "Частный доступ с нулевым доверием",
@@ -211,16 +209,15 @@
"resourcesSearch": "Поиск ресурсов...", "resourcesSearch": "Поиск ресурсов...",
"resourceAdd": "Добавить ресурс", "resourceAdd": "Добавить ресурс",
"resourceErrorDelte": "Ошибка при удалении ресурса", "resourceErrorDelte": "Ошибка при удалении ресурса",
"resourcePoliciesTitle": "Manage Public Resource Policies", "resourcePoliciesTitle": "Управление политиками ресурсов",
"resourcePoliciesAttachedResourcesColumnTitle": "Resources", "resourcePoliciesAttachedResourcesColumnTitle": "Прикрепленные ресурсы",
"resourcePoliciesAttachedResources": "{count} ресурс(ов)", "resourcePoliciesAttachedResources": "{count} ресурс(ов)",
"resourcePoliciesAttachedResourcesCount": "{count, plural, one {# resource} other {# resources}}",
"resourcePoliciesAttachedResourcesEmpty": "нет ресурсов", "resourcePoliciesAttachedResourcesEmpty": "нет ресурсов",
"resourcePoliciesDescription": "Create and manage authentication policies to control access to your public resources", "resourcePoliciesDescription": "Создавайте и управляйте политиками аутентификации для контроля доступа к вашим ресурсам",
"resourcePoliciesSearch": "Поиск политик...", "resourcePoliciesSearch": "Поиск политик...",
"resourcePoliciesAdd": "Добавить политику", "resourcePoliciesAdd": "Добавить политику",
"resourcePoliciesDefaultBadgeText": "Политика по умолчанию", "resourcePoliciesDefaultBadgeText": "Политика по умолчанию",
"resourcePoliciesCreate": "Create Public Resource Policy", "resourcePoliciesCreate": "Создать политику ресурса",
"resourcePoliciesCreateDescription": "Следуйте шагам ниже, чтобы создать новую политику", "resourcePoliciesCreateDescription": "Следуйте шагам ниже, чтобы создать новую политику",
"resourcePolicyName": "Имя политики", "resourcePolicyName": "Имя политики",
"resourcePolicyNameDescription": "Дайте этой политике имя для идентификации ее в ваших ресурсах", "resourcePolicyNameDescription": "Дайте этой политике имя для идентификации ее в ваших ресурсах",
@@ -314,7 +311,7 @@
"rules": "Правила", "rules": "Правила",
"resourceSettingDescription": "Настройка параметров ресурса", "resourceSettingDescription": "Настройка параметров ресурса",
"resourceSetting": "Настройки {resourceName}", "resourceSetting": "Настройки {resourceName}",
"resourcePolicySettingDescription": "Configure the settings on this public resource policy", "resourcePolicySettingDescription": "Настройка параметров политики ресурса",
"resourcePolicySetting": "Настройки {policyName}", "resourcePolicySetting": "Настройки {policyName}",
"alwaysAllow": "Авторизация байпасса", "alwaysAllow": "Авторизация байпасса",
"alwaysDeny": "Блокировать доступ", "alwaysDeny": "Блокировать доступ",
@@ -756,11 +753,11 @@
"rulesErrorDuplicate": "Дублирующее правило", "rulesErrorDuplicate": "Дублирующее правило",
"rulesErrorDuplicateDescription": "Правило с такими настройками уже существует", "rulesErrorDuplicateDescription": "Правило с такими настройками уже существует",
"rulesErrorInvalidIpAddressRange": "Неверный CIDR", "rulesErrorInvalidIpAddressRange": "Неверный CIDR",
"rulesErrorInvalidIpAddressRangeDescription": "Enter a valid CIDR range (e.g., 10.0.0.0/8).", "rulesErrorInvalidIpAddressRangeDescription": "Пожалуйста, введите корректное значение CIDR",
"rulesErrorInvalidUrl": "Invalid path", "rulesErrorInvalidUrl": "Неверный URL путь",
"rulesErrorInvalidUrlDescription": "Enter a valid URL path or pattern (e.g., /api/*).", "rulesErrorInvalidUrlDescription": "Пожалуйста, введите корректное значение URL пути",
"rulesErrorInvalidIpAddress": "Invalid IP address", "rulesErrorInvalidIpAddress": "Неверный IP",
"rulesErrorInvalidIpAddressDescription": "Enter a valid IPv4 or IPv6 address.", "rulesErrorInvalidIpAddressDescription": "Пожалуйста, введите корректный IP адрес",
"rulesErrorUpdate": "Не удалось обновить правила", "rulesErrorUpdate": "Не удалось обновить правила",
"rulesErrorUpdateDescription": "Произошла ошибка при обновлении правил", "rulesErrorUpdateDescription": "Произошла ошибка при обновлении правил",
"rulesUpdated": "Включить правила", "rulesUpdated": "Включить правила",
@@ -768,23 +765,15 @@
"rulesMatchIpAddressRangeDescription": "Введите адрес в формате CIDR (например, 103.21.244.0/22)", "rulesMatchIpAddressRangeDescription": "Введите адрес в формате CIDR (например, 103.21.244.0/22)",
"rulesMatchIpAddress": "Введите IP адрес (например, 103.21.244.12)", "rulesMatchIpAddress": "Введите IP адрес (например, 103.21.244.12)",
"rulesMatchUrl": "Введите URL путь или шаблон (например, /api/v1/todos или /api/v1/*)", "rulesMatchUrl": "Введите URL путь или шаблон (например, /api/v1/todos или /api/v1/*)",
"rulesErrorInvalidPriority": "Invalid priority", "rulesErrorInvalidPriority": "Неверный приоритет",
"rulesErrorInvalidPriorityDescription": "Enter a whole number of 1 or higher.", "rulesErrorInvalidPriorityDescription": "Пожалуйста, введите корректный приоритет",
"rulesErrorDuplicatePriority": "Duplicate priorities", "rulesErrorDuplicatePriority": "Дублирующие приоритеты",
"rulesErrorDuplicatePriorityDescription": "Each rule must have a unique priority number.", "rulesErrorDuplicatePriorityDescription": "Пожалуйста, введите уникальные приоритеты",
"rulesErrorValidation": "Invalid rules",
"rulesErrorValidationRuleDescription": "Rule {ruleNumber}: {message}",
"rulesErrorValueRequired": "Enter a value for this rule.",
"rulesErrorInvalidCountry": "Invalid country",
"rulesErrorInvalidCountryDescription": "Select a valid country.",
"rulesErrorInvalidAsn": "Invalid ASN",
"rulesErrorInvalidAsnDescription": "Enter a valid ASN (e.g., AS15169).",
"ruleUpdated": "Правила обновлены", "ruleUpdated": "Правила обновлены",
"ruleUpdatedDescription": "Правила успешно обновлены", "ruleUpdatedDescription": "Правила успешно обновлены",
"ruleErrorUpdate": "Операция не удалась", "ruleErrorUpdate": "Операция не удалась",
"ruleErrorUpdateDescription": "Произошла ошибка во время операции сохранения", "ruleErrorUpdateDescription": "Произошла ошибка во время операции сохранения",
"rulesPriority": "Приоритет", "rulesPriority": "Приоритет",
"rulesReorderDragHandle": "Drag to reorder rule priority",
"rulesAction": "Действие", "rulesAction": "Действие",
"rulesMatchType": "Тип совпадения", "rulesMatchType": "Тип совпадения",
"value": "Значение", "value": "Значение",
@@ -803,7 +792,7 @@
"rulesResource": "Конфигурация правил ресурса", "rulesResource": "Конфигурация правил ресурса",
"rulesResourceDescription": "Настройка правил для контроля доступа к ресурсу", "rulesResourceDescription": "Настройка правил для контроля доступа к ресурсу",
"ruleSubmit": "Добавить правило", "ruleSubmit": "Добавить правило",
"rulesNoOne": "No rules yet.", "rulesNoOne": "Нет правил. Добавьте правило с помощью формы.",
"rulesOrder": "Правила оцениваются по приоритету в возрастающем порядке.", "rulesOrder": "Правила оцениваются по приоритету в возрастающем порядке.",
"rulesSubmit": "Сохранить правила", "rulesSubmit": "Сохранить правила",
"policyErrorCreate": "Ошибка создания политики", "policyErrorCreate": "Ошибка создания политики",
@@ -814,44 +803,7 @@
"policyErrorUpdateMessageDescription": "Произошла неожиданная ошибка", "policyErrorUpdateMessageDescription": "Произошла неожиданная ошибка",
"policyCreatedSuccess": "Политика ресурса успешно создана", "policyCreatedSuccess": "Политика ресурса успешно создана",
"policyUpdatedSuccess": "Политика ресурса успешно обновлена", "policyUpdatedSuccess": "Политика ресурса успешно обновлена",
"authMethodsSave": "Save Settings", "authMethodsSave": "Сохранить методы аутентификации",
"policyAuthStackTitle": "Authentication",
"policyAuthStackDescription": "Control which authentication methods are required to access this resource",
"policyAuthOrLogicTitle": "Multiple authentication methods active",
"policyAuthOrLogicBanner": "Visitors may authenticate using any one of the active methods below. They do not need to complete all of them.",
"policyAuthMethodActive": "Active",
"policyAuthMethodOff": "Off",
"policyAuthSsoTitle": "Platform SSO",
"policyAuthSsoDescription": "Require sign-in through your organization's identity provider",
"policyAuthSsoSummary": "{idp} · {users} users, {roles} roles",
"policyAuthSsoDefaultIdp": "Default provider",
"policyAuthAddDefaultIdentityProvider": "Add Default Identity Provider",
"policyAuthOtherMethodsTitle": "Other Methods",
"policyAuthOtherMethodsDescription": "Optional methods visitors can use instead of or alongside platform SSO",
"policyAuthPasscodeTitle": "Passcode",
"policyAuthPasscodeDescription": "Require a shared alphanumeric passcode to access the resource",
"policyAuthPasscodeSummary": "Passcode set",
"policyAuthPincodeTitle": "PIN Code",
"policyAuthPincodeDescription": "A short numeric code required to access the resource",
"policyAuthPincodeSummary": "6-digit PIN set",
"policyAuthEmailTitle": "Email Whitelist",
"policyAuthEmailDescription": "Allow listed email addresses with one-time passwords",
"policyAuthEmailSummary": "{count} addresses allowed",
"policyAuthEmailOtpCallout": "Enabling email whitelist sends a one-time password to the visitor's email on login.",
"policyAuthHeaderAuthTitle": "Basic Header Auth",
"policyAuthHeaderAuthDescription": "Validate a custom HTTP header name and value on each request",
"policyAuthHeaderAuthSummary": "Header configured",
"policyAuthHeaderName": "Header name",
"policyAuthHeaderValue": "Expected value",
"policyAccessRulesTitle": "Access Rules",
"policyAccessRulesEnableDescription": "When enabled, rules are evaluated in descending order until one evaluates as true.",
"policyAccessRulesFirstMatch": "Rules are evaluated top to bottom. The first matching rule decides the outcome.",
"policyAccessRulesHowItWorks": "Rules match requests by path, IP address, location, or other criteria. Each rule applies an action: bypass authentication, block access, or pass to authentication. If no rule matches, traffic continues to authentication.",
"policyAccessRulesFallthroughOff": "When rules are disabled, all traffic passes through to authentication.",
"policyAccessRulesFallthroughOn": "When no rule matches, traffic passes through to authentication.",
"rulesPlaceholderCidr": "10.0.0.0/8",
"rulesPlaceholderPath": "/admin/*",
"rulesPlaceholderGeo": "RU, KP",
"rulesSave": "Сохранить правила", "rulesSave": "Сохранить правила",
"resourceErrorCreate": "Ошибка при создании ресурса", "resourceErrorCreate": "Ошибка при создании ресурса",
"resourceErrorCreateDescription": "Произошла ошибка при создании ресурса", "resourceErrorCreateDescription": "Произошла ошибка при создании ресурса",
@@ -1268,10 +1220,8 @@
"addLabels": "Добавить метки", "addLabels": "Добавить метки",
"siteLabelsTab": "Метки", "siteLabelsTab": "Метки",
"siteLabelsDescription": "Управляйте метками, связанными с этим сайтом.", "siteLabelsDescription": "Управляйте метками, связанными с этим сайтом.",
"labelsNotFound": "No labels found.", "labelsNotFound": "Метки не найдены",
"labelsEmptyCreateHint": "Start typing above to create a label.",
"labelSearch": "Поиск меток", "labelSearch": "Поиск меток",
"labelSearchOrCreate": "Search or create a label",
"accessLabelFilterCount": "{count, plural, one {# метка} few {# метки} many {# меток} other {# меток}}", "accessLabelFilterCount": "{count, plural, one {# метка} few {# метки} many {# меток} other {# меток}}",
"labelOverflowCount": "+{count, plural, one {# метка} few {# метки} many {# меток} other {# меток}}", "labelOverflowCount": "+{count, plural, one {# метка} few {# метки} many {# меток} other {# меток}}",
"accessLabelFilterClear": "Очистить фильтры меток", "accessLabelFilterClear": "Очистить фильтры меток",
@@ -1512,7 +1462,7 @@
"sidebarProxyResources": "Публичный", "sidebarProxyResources": "Публичный",
"sidebarClientResources": "Приватный", "sidebarClientResources": "Приватный",
"sidebarPolicies": "Политики", "sidebarPolicies": "Политики",
"sidebarResourcePolicies": "Public Resources", "sidebarResourcePolicies": "Ресурсы",
"sidebarAccessControl": "Контроль доступа", "sidebarAccessControl": "Контроль доступа",
"sidebarLogsAndAnalytics": "Журналы и аналитика", "sidebarLogsAndAnalytics": "Журналы и аналитика",
"sidebarTeam": "Команда", "sidebarTeam": "Команда",
@@ -1520,7 +1470,7 @@
"sidebarAdmin": "Админ", "sidebarAdmin": "Админ",
"sidebarInvitations": "Приглашения", "sidebarInvitations": "Приглашения",
"sidebarRoles": "Роли", "sidebarRoles": "Роли",
"sidebarShareableLinks": "Share Links", "sidebarShareableLinks": "Ссылки",
"sidebarApiKeys": "API ключи", "sidebarApiKeys": "API ключи",
"sidebarProvisioning": "Подготовка", "sidebarProvisioning": "Подготовка",
"sidebarSettings": "Настройки", "sidebarSettings": "Настройки",
@@ -1697,7 +1647,7 @@
"standaloneHcFilterResourceIdFallback": "Ресурс {id}", "standaloneHcFilterResourceIdFallback": "Ресурс {id}",
"blueprints": "Чертежи", "blueprints": "Чертежи",
"blueprintsLog": "Журнал чертежей", "blueprintsLog": "Журнал чертежей",
"blueprintsDescription": "View past blueprint applications and their results or apply a new blueprint", "blueprintsDescription": "Просмотр прошлых применений чертежа и их результатов",
"blueprintAdd": "Добавить чертёж", "blueprintAdd": "Добавить чертёж",
"blueprintGoBack": "Посмотреть все чертежи", "blueprintGoBack": "Посмотреть все чертежи",
"blueprintCreate": "Создать чертёж", "blueprintCreate": "Создать чертёж",
@@ -2077,13 +2027,13 @@
"healthCheckUnknown": "Неизвестно", "healthCheckUnknown": "Неизвестно",
"healthCheck": "Проверка здоровья", "healthCheck": "Проверка здоровья",
"configureHealthCheck": "Настроить проверку здоровья", "configureHealthCheck": "Настроить проверку здоровья",
"configureHealthCheckDescription": "Set up monitoring for your resource to ensure it is always available", "configureHealthCheckDescription": "Настройте мониторинг состояния для {target}",
"enableHealthChecks": "Включить проверки здоровья", "enableHealthChecks": "Включить проверки здоровья",
"healthCheckDisabledStateDescription": "Когда отключен, сайт не будет выполнять проверки состояния и состояние будет считаться неизвестным.", "healthCheckDisabledStateDescription": "Когда отключен, сайт не будет выполнять проверки состояния и состояние будет считаться неизвестным.",
"enableHealthChecksDescription": "Мониторинг здоровья этой цели. При необходимости можно контролировать другую конечную точку.", "enableHealthChecksDescription": "Мониторинг здоровья этой цели. При необходимости можно контролировать другую конечную точку.",
"healthScheme": "Метод", "healthScheme": "Метод",
"healthSelectScheme": "Выберите метод", "healthSelectScheme": "Выберите метод",
"healthCheckPortInvalid": "Port must be between 1 and 65535", "healthCheckPortInvalid": "Порт проверки здоровья должен быть от 1 до 65535",
"healthCheckPath": "Путь", "healthCheckPath": "Путь",
"healthHostname": "IP / хост", "healthHostname": "IP / хост",
"healthPort": "Порт", "healthPort": "Порт",
@@ -2096,7 +2046,6 @@
"requireDeviceApproval": "Требовать подтверждения устройства", "requireDeviceApproval": "Требовать подтверждения устройства",
"requireDeviceApprovalDescription": "Пользователям с этой ролью нужны новые устройства, одобренные администратором, прежде чем они смогут подключаться и получать доступ к ресурсам.", "requireDeviceApprovalDescription": "Пользователям с этой ролью нужны новые устройства, одобренные администратором, прежде чем они смогут подключаться и получать доступ к ресурсам.",
"sshSettings": "Настройки SSH", "sshSettings": "Настройки SSH",
"sshAccess": "SSH Access",
"rdpSettings": "Настройки RDP", "rdpSettings": "Настройки RDP",
"vncSettings": "Настройки VNC", "vncSettings": "Настройки VNC",
"sshServer": "SSH сервер", "sshServer": "SSH сервер",
@@ -2123,13 +2072,8 @@
"sshDaemonDisclaimer": "Убедитесь, что целевой хост правильно настроен для запуска демона аутентификации перед завершением этой настройки, иначе предоставление не удастся.", "sshDaemonDisclaimer": "Убедитесь, что целевой хост правильно настроен для запуска демона аутентификации перед завершением этой настройки, иначе предоставление не удастся.",
"sshDaemonPort": "Порт демона", "sshDaemonPort": "Порт демона",
"sshServerDestination": "Пункт назначения сервера", "sshServerDestination": "Пункт назначения сервера",
"sshServerDestinationDescription": "Configure the destination of the SSH server", "sshServerDestinationDescription": "Настройте пункт назначения и порт SSH-сервера",
"destination": "Пункт назначения", "destination": "Пункт назначения",
"destinationRequired": "Destination is required.",
"domainRequired": "Domain is required.",
"proxyPortRequired": "Port is required.",
"invalidPathConfiguration": "Invalid path configuration.",
"invalidRewritePathConfiguration": "Invalid rewrite path configuration.",
"bgTargetMultiSiteDisclaimer": "Выбор нескольких сайтов включает в себя устойчивую маршрутизацию и автоматический отказ для обеспечения высокой доступности.", "bgTargetMultiSiteDisclaimer": "Выбор нескольких сайтов включает в себя устойчивую маршрутизацию и автоматический отказ для обеспечения высокой доступности.",
"roleAllowSsh": "Разрешить SSH", "roleAllowSsh": "Разрешить SSH",
"roleAllowSshAllow": "Разрешить", "roleAllowSshAllow": "Разрешить",
@@ -3090,7 +3034,7 @@
"enterConfirmation": "Введите подтверждение", "enterConfirmation": "Введите подтверждение",
"blueprintViewDetails": "Подробности", "blueprintViewDetails": "Подробности",
"defaultIdentityProvider": "Поставщик удостоверений по умолчанию", "defaultIdentityProvider": "Поставщик удостоверений по умолчанию",
"defaultIdentityProviderDescription": "The user will be automatically redirected to this identity provider for authentication.", "defaultIdentityProviderDescription": "Когда выбран поставщик идентификации по умолчанию, пользователь будет автоматически перенаправлен на провайдер для аутентификации.",
"editInternalResourceDialogNetworkSettings": "Настройки сети", "editInternalResourceDialogNetworkSettings": "Настройки сети",
"editInternalResourceDialogAccessPolicy": "Политика доступа", "editInternalResourceDialogAccessPolicy": "Политика доступа",
"editInternalResourceDialogAddRoles": "Добавить роли", "editInternalResourceDialogAddRoles": "Добавить роли",
@@ -3402,8 +3346,6 @@
"idpUnassociateQuestion": "Вы уверены, что хотите рассоединить этого поставщика удостоверений с этой организацией?", "idpUnassociateQuestion": "Вы уверены, что хотите рассоединить этого поставщика удостоверений с этой организацией?",
"idpUnassociateDescription": "Все пользователи, связанные с этим поставщиком удостоверений, будут удалены из этой организации, но поставщик удостоверений будет продолжать существовать для других связанных организаций.", "idpUnassociateDescription": "Все пользователи, связанные с этим поставщиком удостоверений, будут удалены из этой организации, но поставщик удостоверений будет продолжать существовать для других связанных организаций.",
"idpUnassociateConfirm": "Подтвердите рассоединение поставщика удостоверений", "idpUnassociateConfirm": "Подтвердите рассоединение поставщика удостоверений",
"idpConfirmDeleteAndRemoveMeFromOrg": "DELETE AND REMOVE ME FROM ORG",
"idpUnassociateAndRemoveMeFromOrg": "UNASSOCIATE AND REMOVE ME FROM ORG",
"idpUnassociateWarning": "Это не может быть отменено для этой организации.", "idpUnassociateWarning": "Это не может быть отменено для этой организации.",
"idpUnassociatedDescription": "Поставщик удостоверений успешно рассоединен с этой организацией", "idpUnassociatedDescription": "Поставщик удостоверений успешно рассоединен с этой организацией",
"idpUnassociateMenu": "Рассоединить", "idpUnassociateMenu": "Рассоединить",
@@ -3510,43 +3452,5 @@
"sshErrorNoTarget": "Цель не указана", "sshErrorNoTarget": "Цель не указана",
"sshErrorWebSocket": "Подключение WebSocket не удалось", "sshErrorWebSocket": "Подключение WebSocket не удалось",
"sshErrorAuthFailed": "Ошибка аутентификации", "sshErrorAuthFailed": "Ошибка аутентификации",
"sshErrorConnectionClosed": "Подключение закрыто до завершения аутентификации", "sshErrorConnectionClosed": "Подключение закрыто до завершения аутентификации"
"sitePangolinSshDescription": "Allow SSH access to resources on this site. This can be changed later.",
"browserGatewayNoResourceForDomain": "No resource found for this domain",
"browserGatewayNoTarget": "No target",
"browserGatewayConnect": "Connect",
"browserGatewayCtrlAltDel": "Ctrl+Alt+Del",
"sshErrorSignKeyFailed": "Failed to sign SSH key for PAM push authentication. Did you sign in as a user?",
"sshTerminalError": "Error: {error}",
"sshConnectionClosedCode": "Connection closed (code {code})",
"sshPrivateKeyPlaceholder": "-----BEGIN OPENSSH PRIVATE KEY-----",
"sshPrivateKeyRequired": "Private key is required",
"vncTitle": "VNC",
"vncSignInDescription": "Enter your VNC password to connect",
"vncPasswordOptional": "Password (optional)",
"vncNoResourceTarget": "No resource target is available",
"vncFailedToLoadNovnc": "Failed to load noVNC",
"vncAuthFailedStatus": "Status {status}",
"vncPasteClipboard": "Paste clipboard",
"rdpTitle": "RDP",
"rdpSignInTitle": "Sign in to Remote Desktop",
"rdpSignInDescription": "Enter Windows credentials to connect",
"rdpLoadingModule": "Loading module...",
"rdpFailedToLoadModule": "Failed to load RDP module",
"rdpNotReady": "Not ready",
"rdpModuleInitializing": "RDP module is still initializing",
"rdpDownloadingFiles": "Downloading {count} file(s) from remote…",
"rdpDownloadFailed": "Download failed: {fileName}",
"rdpUploaded": "Uploaded: {fileName}",
"rdpNoConnectionTarget": "No connection target available",
"rdpConnectionFailed": "Connection failed",
"rdpFit": "Fit",
"rdpFull": "Full",
"rdpReal": "Real",
"rdpMeta": "Meta",
"rdpUploadFiles": "Upload files",
"rdpFilesReadyToPaste": "Files ready to paste",
"rdpFilesReadyToPasteDescription": "{count} file(s) copied to remote clipboard — press Ctrl+V on the remote desktop to paste.",
"rdpUploadFailed": "Upload failed",
"rdpUnicodeKeyboardMode": "Unicode keyboard mode"
} }
+27 -123
View File
@@ -101,8 +101,6 @@
"sitesTableViewPrivateResources": "Özel Kaynakları Görüntüle", "sitesTableViewPrivateResources": "Özel Kaynakları Görüntüle",
"siteInstallNewt": "Newt Yükle", "siteInstallNewt": "Newt Yükle",
"siteInstallNewtDescription": "Newt'i sisteminizde çalıştırma", "siteInstallNewtDescription": "Newt'i sisteminizde çalıştırma",
"siteInstallKubernetesDocsDescription": "For more and up to date Kubernetes installation information, see <docsLink>docs.pangolin.net/manage/sites/install-kubernetes</docsLink>.",
"siteInstallAdvantechDocsDescription": "For Advantech modem installation instructions, see <docsLink>docs.pangolin.net/manage/sites/install-advantech</docsLink>.",
"WgConfiguration": "WireGuard Yapılandırması", "WgConfiguration": "WireGuard Yapılandırması",
"WgConfigurationDescription": "Ağınıza bağlanmak için aşağıdaki yapılandırmayı kullanın", "WgConfigurationDescription": "Ağınıza bağlanmak için aşağıdaki yapılandırmayı kullanın",
"operatingSystem": "İşletim Sistemi", "operatingSystem": "İşletim Sistemi",
@@ -202,8 +200,8 @@
"shareErrorSelectResource": "Lütfen bir kaynak seçin", "shareErrorSelectResource": "Lütfen bir kaynak seçin",
"proxyResourceTitle": "Herkese Açık Kaynakları Yönet", "proxyResourceTitle": "Herkese Açık Kaynakları Yönet",
"proxyResourceDescription": "Bir web tarayıcısı aracılığıyla kamuya açık kaynaklar oluşturun ve yönetin", "proxyResourceDescription": "Bir web tarayıcısı aracılığıyla kamuya açık kaynaklar oluşturun ve yönetin",
"publicResourcesBannerTitle": "Web-based Public Access", "publicResourcesBannerTitle": "Web Tabanlı Genel Erişim",
"publicResourcesBannerDescription": "Public resources are HTTPS proxies accessible to anyone on the internet through a web browser. Unlike private resources, they do not require client-side software and can include identity and context-aware access policies.", "publicResourcesBannerDescription": "Genel kaynaklar, web tarayıcısı aracılığıyla herkesin internette erişebileceği HTTPS veya TCP/UDP proxy'leridir. Özel kaynakların aksine, istemci tarafı yazılıma ihtiyaç duymazlar ve kimlik ve bağlam farkındalığı erişim politikalarını içerebilirler.",
"clientResourceTitle": "Özel Kaynakları Yönet", "clientResourceTitle": "Özel Kaynakları Yönet",
"clientResourceDescription": "Sadece bağlı bir istemci aracılığıyla erişilebilen kaynakları oluşturun ve yönetin", "clientResourceDescription": "Sadece bağlı bir istemci aracılığıyla erişilebilen kaynakları oluşturun ve yönetin",
"privateResourcesBannerTitle": "Sıfır Güven Özel Erişim", "privateResourcesBannerTitle": "Sıfır Güven Özel Erişim",
@@ -211,16 +209,15 @@
"resourcesSearch": "Kaynakları ara...", "resourcesSearch": "Kaynakları ara...",
"resourceAdd": "Kaynak Ekle", "resourceAdd": "Kaynak Ekle",
"resourceErrorDelte": "Kaynak silinirken hata", "resourceErrorDelte": "Kaynak silinirken hata",
"resourcePoliciesTitle": "Manage Public Resource Policies", "resourcePoliciesTitle": "Kaynak Politikalarını Yönet",
"resourcePoliciesAttachedResourcesColumnTitle": "Resources", "resourcePoliciesAttachedResourcesColumnTitle": "Ekteki kaynaklar",
"resourcePoliciesAttachedResources": "{count} kaynak", "resourcePoliciesAttachedResources": "{count} kaynak",
"resourcePoliciesAttachedResourcesCount": "{count, plural, one {# resource} other {# resources}}",
"resourcePoliciesAttachedResourcesEmpty": "hiçbir kaynak", "resourcePoliciesAttachedResourcesEmpty": "hiçbir kaynak",
"resourcePoliciesDescription": "Create and manage authentication policies to control access to your public resources", "resourcePoliciesDescription": "Kaynaklarınıza erişimi kontrol etmek için kimlik doğrulama politikaları oluşturun ve yönetin",
"resourcePoliciesSearch": "Politikaları ara...", "resourcePoliciesSearch": "Politikaları ara...",
"resourcePoliciesAdd": "Politika Ekle", "resourcePoliciesAdd": "Politika Ekle",
"resourcePoliciesDefaultBadgeText": "Varsayılan politika", "resourcePoliciesDefaultBadgeText": "Varsayılan politika",
"resourcePoliciesCreate": "Create Public Resource Policy", "resourcePoliciesCreate": "Kaynak Politikası Oluştur",
"resourcePoliciesCreateDescription": "Yeni bir politika oluşturmak için aşağıdaki adımları izleyin", "resourcePoliciesCreateDescription": "Yeni bir politika oluşturmak için aşağıdaki adımları izleyin",
"resourcePolicyName": "Politika Adı", "resourcePolicyName": "Politika Adı",
"resourcePolicyNameDescription": "Bu politikaya kaynaklarınız arasında kolayca tanımlayabilmek için bir ad verin", "resourcePolicyNameDescription": "Bu politikaya kaynaklarınız arasında kolayca tanımlayabilmek için bir ad verin",
@@ -314,7 +311,7 @@
"rules": "Kurallar", "rules": "Kurallar",
"resourceSettingDescription": "Kaynağınızdaki ayarları yapılandırın", "resourceSettingDescription": "Kaynağınızdaki ayarları yapılandırın",
"resourceSetting": "{resourceName} Ayarları", "resourceSetting": "{resourceName} Ayarları",
"resourcePolicySettingDescription": "Configure the settings on this public resource policy", "resourcePolicySettingDescription": "Kaynak politikası üzerindeki ayarları yapılandır",
"resourcePolicySetting": "{policyName} Ayarları", "resourcePolicySetting": "{policyName} Ayarları",
"alwaysAllow": "Kimlik Doğrulamayı Atla", "alwaysAllow": "Kimlik Doğrulamayı Atla",
"alwaysDeny": "Erişimi Engelle", "alwaysDeny": "Erişimi Engelle",
@@ -756,11 +753,11 @@
"rulesErrorDuplicate": "Yinelenen kural", "rulesErrorDuplicate": "Yinelenen kural",
"rulesErrorDuplicateDescription": "Bu ayarlara sahip bir kural zaten mevcut", "rulesErrorDuplicateDescription": "Bu ayarlara sahip bir kural zaten mevcut",
"rulesErrorInvalidIpAddressRange": "Geçersiz CIDR", "rulesErrorInvalidIpAddressRange": "Geçersiz CIDR",
"rulesErrorInvalidIpAddressRangeDescription": "Enter a valid CIDR range (e.g., 10.0.0.0/8).", "rulesErrorInvalidIpAddressRangeDescription": "Lütfen geçerli bir CIDR değeri girin",
"rulesErrorInvalidUrl": "Invalid path", "rulesErrorInvalidUrl": "Geçersiz URL yolu",
"rulesErrorInvalidUrlDescription": "Enter a valid URL path or pattern (e.g., /api/*).", "rulesErrorInvalidUrlDescription": "Lütfen geçerli bir URL yolu değeri girin",
"rulesErrorInvalidIpAddress": "Invalid IP address", "rulesErrorInvalidIpAddress": "Geçersiz IP",
"rulesErrorInvalidIpAddressDescription": "Enter a valid IPv4 or IPv6 address.", "rulesErrorInvalidIpAddressDescription": "Lütfen geçerli bir IP adresi girin",
"rulesErrorUpdate": "Kurallar güncellenemedi", "rulesErrorUpdate": "Kurallar güncellenemedi",
"rulesErrorUpdateDescription": "Kurallar güncellenirken bir hata oluştu", "rulesErrorUpdateDescription": "Kurallar güncellenirken bir hata oluştu",
"rulesUpdated": "Kuralları Etkinleştir", "rulesUpdated": "Kuralları Etkinleştir",
@@ -768,23 +765,15 @@
"rulesMatchIpAddressRangeDescription": "CIDR formatında bir adres girin (örneğin, 103.21.244.0/22)", "rulesMatchIpAddressRangeDescription": "CIDR formatında bir adres girin (örneğin, 103.21.244.0/22)",
"rulesMatchIpAddress": "Bir IP adresi girin (örneğin, 103.21.244.12)", "rulesMatchIpAddress": "Bir IP adresi girin (örneğin, 103.21.244.12)",
"rulesMatchUrl": "Bir URL yolu veya deseni girin (örneğin, /api/v1/todos veya /api/v1/*)", "rulesMatchUrl": "Bir URL yolu veya deseni girin (örneğin, /api/v1/todos veya /api/v1/*)",
"rulesErrorInvalidPriority": "Invalid priority", "rulesErrorInvalidPriority": "Geçersiz Öncelik",
"rulesErrorInvalidPriorityDescription": "Enter a whole number of 1 or higher.", "rulesErrorInvalidPriorityDescription": "Lütfen geçerli bir öncelik girin",
"rulesErrorDuplicatePriority": "Duplicate priorities", "rulesErrorDuplicatePriority": "Yinelenen Öncelikler",
"rulesErrorDuplicatePriorityDescription": "Each rule must have a unique priority number.", "rulesErrorDuplicatePriorityDescription": "Lütfen benzersiz öncelikler girin",
"rulesErrorValidation": "Invalid rules",
"rulesErrorValidationRuleDescription": "Rule {ruleNumber}: {message}",
"rulesErrorValueRequired": "Enter a value for this rule.",
"rulesErrorInvalidCountry": "Invalid country",
"rulesErrorInvalidCountryDescription": "Select a valid country.",
"rulesErrorInvalidAsn": "Invalid ASN",
"rulesErrorInvalidAsnDescription": "Enter a valid ASN (e.g., AS15169).",
"ruleUpdated": "Kurallar güncellendi", "ruleUpdated": "Kurallar güncellendi",
"ruleUpdatedDescription": "Kurallar başarıyla güncellendi", "ruleUpdatedDescription": "Kurallar başarıyla güncellendi",
"ruleErrorUpdate": "Operasyon başarısız oldu", "ruleErrorUpdate": "Operasyon başarısız oldu",
"ruleErrorUpdateDescription": "Kaydetme operasyonu sırasında bir hata oluştu", "ruleErrorUpdateDescription": "Kaydetme operasyonu sırasında bir hata oluştu",
"rulesPriority": "Öncelik", "rulesPriority": "Öncelik",
"rulesReorderDragHandle": "Drag to reorder rule priority",
"rulesAction": "Aksiyon", "rulesAction": "Aksiyon",
"rulesMatchType": "Eşleşme Türü", "rulesMatchType": "Eşleşme Türü",
"value": "Değer", "value": "Değer",
@@ -803,7 +792,7 @@
"rulesResource": "Kaynak Kuralları Yapılandırması", "rulesResource": "Kaynak Kuralları Yapılandırması",
"rulesResourceDescription": "Kaynağa erişimi kontrol etmek için kuralları yapılandırın", "rulesResourceDescription": "Kaynağa erişimi kontrol etmek için kuralları yapılandırın",
"ruleSubmit": "Kural Ekle", "ruleSubmit": "Kural Ekle",
"rulesNoOne": "No rules yet.", "rulesNoOne": "Kural yok. Formu kullanarak bir kural ekleyin.",
"rulesOrder": "Kurallar, artan öncelik sırasına göre değerlendirilir.", "rulesOrder": "Kurallar, artan öncelik sırasına göre değerlendirilir.",
"rulesSubmit": "Kuralları Kaydet", "rulesSubmit": "Kuralları Kaydet",
"policyErrorCreate": "Politika oluşturulurken hata oluştu", "policyErrorCreate": "Politika oluşturulurken hata oluştu",
@@ -814,44 +803,7 @@
"policyErrorUpdateMessageDescription": "Beklenmeyen bir hata oluştu", "policyErrorUpdateMessageDescription": "Beklenmeyen bir hata oluştu",
"policyCreatedSuccess": "Kaynak politikası başarıyla oluşturuldu", "policyCreatedSuccess": "Kaynak politikası başarıyla oluşturuldu",
"policyUpdatedSuccess": "Kaynak politikası başarıyla güncellendi", "policyUpdatedSuccess": "Kaynak politikası başarıyla güncellendi",
"authMethodsSave": "Save Settings", "authMethodsSave": "Kimlik doğrulama yöntemlerini kaydet",
"policyAuthStackTitle": "Authentication",
"policyAuthStackDescription": "Control which authentication methods are required to access this resource",
"policyAuthOrLogicTitle": "Multiple authentication methods active",
"policyAuthOrLogicBanner": "Visitors may authenticate using any one of the active methods below. They do not need to complete all of them.",
"policyAuthMethodActive": "Active",
"policyAuthMethodOff": "Off",
"policyAuthSsoTitle": "Platform SSO",
"policyAuthSsoDescription": "Require sign-in through your organization's identity provider",
"policyAuthSsoSummary": "{idp} · {users} users, {roles} roles",
"policyAuthSsoDefaultIdp": "Default provider",
"policyAuthAddDefaultIdentityProvider": "Add Default Identity Provider",
"policyAuthOtherMethodsTitle": "Other Methods",
"policyAuthOtherMethodsDescription": "Optional methods visitors can use instead of or alongside platform SSO",
"policyAuthPasscodeTitle": "Passcode",
"policyAuthPasscodeDescription": "Require a shared alphanumeric passcode to access the resource",
"policyAuthPasscodeSummary": "Passcode set",
"policyAuthPincodeTitle": "PIN Code",
"policyAuthPincodeDescription": "A short numeric code required to access the resource",
"policyAuthPincodeSummary": "6-digit PIN set",
"policyAuthEmailTitle": "Email Whitelist",
"policyAuthEmailDescription": "Allow listed email addresses with one-time passwords",
"policyAuthEmailSummary": "{count} addresses allowed",
"policyAuthEmailOtpCallout": "Enabling email whitelist sends a one-time password to the visitor's email on login.",
"policyAuthHeaderAuthTitle": "Basic Header Auth",
"policyAuthHeaderAuthDescription": "Validate a custom HTTP header name and value on each request",
"policyAuthHeaderAuthSummary": "Header configured",
"policyAuthHeaderName": "Header name",
"policyAuthHeaderValue": "Expected value",
"policyAccessRulesTitle": "Access Rules",
"policyAccessRulesEnableDescription": "When enabled, rules are evaluated in descending order until one evaluates as true.",
"policyAccessRulesFirstMatch": "Rules are evaluated top to bottom. The first matching rule decides the outcome.",
"policyAccessRulesHowItWorks": "Rules match requests by path, IP address, location, or other criteria. Each rule applies an action: bypass authentication, block access, or pass to authentication. If no rule matches, traffic continues to authentication.",
"policyAccessRulesFallthroughOff": "When rules are disabled, all traffic passes through to authentication.",
"policyAccessRulesFallthroughOn": "When no rule matches, traffic passes through to authentication.",
"rulesPlaceholderCidr": "10.0.0.0/8",
"rulesPlaceholderPath": "/admin/*",
"rulesPlaceholderGeo": "RU, KP",
"rulesSave": "Kuralları Kaydet", "rulesSave": "Kuralları Kaydet",
"resourceErrorCreate": "Kaynak oluşturma hatası", "resourceErrorCreate": "Kaynak oluşturma hatası",
"resourceErrorCreateDescription": "Kaynak oluşturulurken bir hata oluştu", "resourceErrorCreateDescription": "Kaynak oluşturulurken bir hata oluştu",
@@ -1268,10 +1220,8 @@
"addLabels": "Etiketler ekle", "addLabels": "Etiketler ekle",
"siteLabelsTab": "Etiketler", "siteLabelsTab": "Etiketler",
"siteLabelsDescription": "Bu siteyle ilişkili etiketleri yönetin.", "siteLabelsDescription": "Bu siteyle ilişkili etiketleri yönetin.",
"labelsNotFound": "No labels found.", "labelsNotFound": "Etiketler bulunamadı",
"labelsEmptyCreateHint": "Start typing above to create a label.",
"labelSearch": "Etiket ara", "labelSearch": "Etiket ara",
"labelSearchOrCreate": "Search or create a label",
"accessLabelFilterCount": "{count, plural, one {# etiket} other {# etiketler}}", "accessLabelFilterCount": "{count, plural, one {# etiket} other {# etiketler}}",
"labelOverflowCount": "+{count, plural, one {# etiket} other {# etiketler}}", "labelOverflowCount": "+{count, plural, one {# etiket} other {# etiketler}}",
"accessLabelFilterClear": "Etiket filtrelerini temizle", "accessLabelFilterClear": "Etiket filtrelerini temizle",
@@ -1512,7 +1462,7 @@
"sidebarProxyResources": "Herkese Açık", "sidebarProxyResources": "Herkese Açık",
"sidebarClientResources": "Özel", "sidebarClientResources": "Özel",
"sidebarPolicies": "Politikalar", "sidebarPolicies": "Politikalar",
"sidebarResourcePolicies": "Public Resources", "sidebarResourcePolicies": "Kaynaklar",
"sidebarAccessControl": "Erişim Kontrolü", "sidebarAccessControl": "Erişim Kontrolü",
"sidebarLogsAndAnalytics": "Kayıtlar & Analitik", "sidebarLogsAndAnalytics": "Kayıtlar & Analitik",
"sidebarTeam": "Ekip", "sidebarTeam": "Ekip",
@@ -1520,7 +1470,7 @@
"sidebarAdmin": "Yönetici", "sidebarAdmin": "Yönetici",
"sidebarInvitations": "Davetiye", "sidebarInvitations": "Davetiye",
"sidebarRoles": "Roller", "sidebarRoles": "Roller",
"sidebarShareableLinks": "Share Links", "sidebarShareableLinks": "Bağlantılar",
"sidebarApiKeys": "API Anahtarları", "sidebarApiKeys": "API Anahtarları",
"sidebarProvisioning": "Tedarik", "sidebarProvisioning": "Tedarik",
"sidebarSettings": "Ayarlar", "sidebarSettings": "Ayarlar",
@@ -1697,7 +1647,7 @@
"standaloneHcFilterResourceIdFallback": "Kaynak {id}", "standaloneHcFilterResourceIdFallback": "Kaynak {id}",
"blueprints": "Planlar", "blueprints": "Planlar",
"blueprintsLog": "Şablonlar Günlüğü", "blueprintsLog": "Şablonlar Günlüğü",
"blueprintsDescription": "View past blueprint applications and their results or apply a new blueprint", "blueprintsDescription": "Geçmiş şablon uygulamalarını ve sonuçlarını görüntüleyin",
"blueprintAdd": "Plan Ekle", "blueprintAdd": "Plan Ekle",
"blueprintGoBack": "Tüm Planları Gör", "blueprintGoBack": "Tüm Planları Gör",
"blueprintCreate": "Plan Oluştur", "blueprintCreate": "Plan Oluştur",
@@ -2077,13 +2027,13 @@
"healthCheckUnknown": "Bilinmiyor", "healthCheckUnknown": "Bilinmiyor",
"healthCheck": "Sağlık Kontrolü", "healthCheck": "Sağlık Kontrolü",
"configureHealthCheck": "Sağlık Kontrolünü Yapılandır", "configureHealthCheck": "Sağlık Kontrolünü Yapılandır",
"configureHealthCheckDescription": "Set up monitoring for your resource to ensure it is always available", "configureHealthCheckDescription": "{hedef} için sağlık izleme kurun",
"enableHealthChecks": "Sağlık Kontrollerini Etkinleştir", "enableHealthChecks": "Sağlık Kontrollerini Etkinleştir",
"healthCheckDisabledStateDescription": "Devre dışı bırakıldığında, site sağlık kontrolleri yapmaz ve durum bilinmeyen olarak kabul edilecektir.", "healthCheckDisabledStateDescription": "Devre dışı bırakıldığında, site sağlık kontrolleri yapmaz ve durum bilinmeyen olarak kabul edilecektir.",
"enableHealthChecksDescription": "Bu hedefin sağlığını izleyin. Gerekirse hedef dışındaki bir son noktayı izleyebilirsiniz.", "enableHealthChecksDescription": "Bu hedefin sağlığını izleyin. Gerekirse hedef dışındaki bir son noktayı izleyebilirsiniz.",
"healthScheme": "Yöntem", "healthScheme": "Yöntem",
"healthSelectScheme": "Yöntem Seç", "healthSelectScheme": "Yöntem Seç",
"healthCheckPortInvalid": "Port must be between 1 and 65535", "healthCheckPortInvalid": "Sağlık Kontrolü portu 1 ile 65535 arasında olmalıdır",
"healthCheckPath": "Yol", "healthCheckPath": "Yol",
"healthHostname": "IP / Hostname", "healthHostname": "IP / Hostname",
"healthPort": "Bağlantı Noktası", "healthPort": "Bağlantı Noktası",
@@ -2096,7 +2046,6 @@
"requireDeviceApproval": "Cihaz Onaylarını Gerektir", "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.", "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.",
"sshSettings": "SSH Ayarları", "sshSettings": "SSH Ayarları",
"sshAccess": "SSH Access",
"rdpSettings": "RDP Ayarları", "rdpSettings": "RDP Ayarları",
"vncSettings": "VNC Ayarları", "vncSettings": "VNC Ayarları",
"sshServer": "SSH Sunucusu", "sshServer": "SSH Sunucusu",
@@ -2123,13 +2072,8 @@
"sshDaemonDisclaimer": "Bu kurulumu tamamlamadan önce hedef ana bilgisayarınızın kimlik doğrulama daemonunu çalıştıracak şekilde düzgün yapılandırıldığından emin olun, aksi takdirde sağlama başarısız olur.", "sshDaemonDisclaimer": "Bu kurulumu tamamlamadan önce hedef ana bilgisayarınızın kimlik doğrulama daemonunu çalıştıracak şekilde düzgün yapılandırıldığından emin olun, aksi takdirde sağlama başarısız olur.",
"sshDaemonPort": "Daemon Bağlantı Noktası", "sshDaemonPort": "Daemon Bağlantı Noktası",
"sshServerDestination": "Sunucu Hedefi", "sshServerDestination": "Sunucu Hedefi",
"sshServerDestinationDescription": "Configure the destination of the SSH server", "sshServerDestinationDescription": "SSH sunucusunun hedefini ve bağlantı noktasını yapılandırın",
"destination": "Hedef", "destination": "Hedef",
"destinationRequired": "Destination is required.",
"domainRequired": "Domain is required.",
"proxyPortRequired": "Port is required.",
"invalidPathConfiguration": "Invalid path configuration.",
"invalidRewritePathConfiguration": "Invalid rewrite path configuration.",
"bgTargetMultiSiteDisclaimer": "Birden fazla site seçmek, yüksek erişilebilirlik için dayanıklı yönlendirme ve failover sağlar.", "bgTargetMultiSiteDisclaimer": "Birden fazla site seçmek, yüksek erişilebilirlik için dayanıklı yönlendirme ve failover sağlar.",
"roleAllowSsh": "SSH'a İzin Ver", "roleAllowSsh": "SSH'a İzin Ver",
"roleAllowSshAllow": "İzin Ver", "roleAllowSshAllow": "İzin Ver",
@@ -3090,7 +3034,7 @@
"enterConfirmation": "Onayı girin", "enterConfirmation": "Onayı girin",
"blueprintViewDetails": "Detaylar", "blueprintViewDetails": "Detaylar",
"defaultIdentityProvider": "Varsayılan Kimlik Sağlayıcı", "defaultIdentityProvider": "Varsayılan Kimlik Sağlayıcı",
"defaultIdentityProviderDescription": "The user will be automatically redirected to this identity provider for authentication.", "defaultIdentityProviderDescription": "Varsayılan bir kimlik sağlayıcı seçildiğinde, kullanıcı kimlik doğrulaması için otomatik olarak sağlayıcıya yönlendirilecektir.",
"editInternalResourceDialogNetworkSettings": "Ağ Ayarları", "editInternalResourceDialogNetworkSettings": "Ağ Ayarları",
"editInternalResourceDialogAccessPolicy": "Erişim Politikası", "editInternalResourceDialogAccessPolicy": "Erişim Politikası",
"editInternalResourceDialogAddRoles": "Roller Ekle", "editInternalResourceDialogAddRoles": "Roller Ekle",
@@ -3402,8 +3346,6 @@
"idpUnassociateQuestion": "Bu kimlik sağlayıcının bu kuruluştan ilişiğini kesmek istediğinizden emin misiniz?", "idpUnassociateQuestion": "Bu kimlik sağlayıcının bu kuruluştan ilişiğini kesmek istediğinizden emin misiniz?",
"idpUnassociateDescription": "Bu kimlik sağlayıcı ile ilişkilendirilen tüm kullanıcılar bu kuruluştan kaldırılacaktır, ancak kimlik sağlayıcı diğer ilişkilendirilen kuruluşlar için var olmaya devam edecektir.", "idpUnassociateDescription": "Bu kimlik sağlayıcı ile ilişkilendirilen tüm kullanıcılar bu kuruluştan kaldırılacaktır, ancak kimlik sağlayıcı diğer ilişkilendirilen kuruluşlar için var olmaya devam edecektir.",
"idpUnassociateConfirm": "Kimlik Sağlayıcının İlişkisinin Kesilmesini Onayla", "idpUnassociateConfirm": "Kimlik Sağlayıcının İlişkisinin Kesilmesini Onayla",
"idpConfirmDeleteAndRemoveMeFromOrg": "DELETE AND REMOVE ME FROM ORG",
"idpUnassociateAndRemoveMeFromOrg": "UNASSOCIATE AND REMOVE ME FROM ORG",
"idpUnassociateWarning": "Bu işlem bu kuruluş için geri alınamaz.", "idpUnassociateWarning": "Bu işlem bu kuruluş için geri alınamaz.",
"idpUnassociatedDescription": "Kimlik sağlayıcı bu kuruluştan başarıyla ayrıldı", "idpUnassociatedDescription": "Kimlik sağlayıcı bu kuruluştan başarıyla ayrıldı",
"idpUnassociateMenu": "İlişkiyi Kes", "idpUnassociateMenu": "İlişkiyi Kes",
@@ -3510,43 +3452,5 @@
"sshErrorNoTarget": "Belirtilen hedef yok", "sshErrorNoTarget": "Belirtilen hedef yok",
"sshErrorWebSocket": "WebSocket bağlantısı başarısız oldu", "sshErrorWebSocket": "WebSocket bağlantısı başarısız oldu",
"sshErrorAuthFailed": "Kimlik doğrulama başarısız", "sshErrorAuthFailed": "Kimlik doğrulama başarısız",
"sshErrorConnectionClosed": "Kimlik doğrulama tamamlanmadan bağlantı kapandı", "sshErrorConnectionClosed": "Kimlik doğrulama tamamlanmadan bağlantı kapandı"
"sitePangolinSshDescription": "Allow SSH access to resources on this site. This can be changed later.",
"browserGatewayNoResourceForDomain": "No resource found for this domain",
"browserGatewayNoTarget": "No target",
"browserGatewayConnect": "Connect",
"browserGatewayCtrlAltDel": "Ctrl+Alt+Del",
"sshErrorSignKeyFailed": "Failed to sign SSH key for PAM push authentication. Did you sign in as a user?",
"sshTerminalError": "Error: {error}",
"sshConnectionClosedCode": "Connection closed (code {code})",
"sshPrivateKeyPlaceholder": "-----BEGIN OPENSSH PRIVATE KEY-----",
"sshPrivateKeyRequired": "Private key is required",
"vncTitle": "VNC",
"vncSignInDescription": "Enter your VNC password to connect",
"vncPasswordOptional": "Password (optional)",
"vncNoResourceTarget": "No resource target is available",
"vncFailedToLoadNovnc": "Failed to load noVNC",
"vncAuthFailedStatus": "Status {status}",
"vncPasteClipboard": "Paste clipboard",
"rdpTitle": "RDP",
"rdpSignInTitle": "Sign in to Remote Desktop",
"rdpSignInDescription": "Enter Windows credentials to connect",
"rdpLoadingModule": "Loading module...",
"rdpFailedToLoadModule": "Failed to load RDP module",
"rdpNotReady": "Not ready",
"rdpModuleInitializing": "RDP module is still initializing",
"rdpDownloadingFiles": "Downloading {count} file(s) from remote…",
"rdpDownloadFailed": "Download failed: {fileName}",
"rdpUploaded": "Uploaded: {fileName}",
"rdpNoConnectionTarget": "No connection target available",
"rdpConnectionFailed": "Connection failed",
"rdpFit": "Fit",
"rdpFull": "Full",
"rdpReal": "Real",
"rdpMeta": "Meta",
"rdpUploadFiles": "Upload files",
"rdpFilesReadyToPaste": "Files ready to paste",
"rdpFilesReadyToPasteDescription": "{count} file(s) copied to remote clipboard — press Ctrl+V on the remote desktop to paste.",
"rdpUploadFailed": "Upload failed",
"rdpUnicodeKeyboardMode": "Unicode keyboard mode"
} }
+27 -123
View File
@@ -101,8 +101,6 @@
"sitesTableViewPrivateResources": "查看私有资源", "sitesTableViewPrivateResources": "查看私有资源",
"siteInstallNewt": "安装 Newt", "siteInstallNewt": "安装 Newt",
"siteInstallNewtDescription": "在您的系统中运行 Newt", "siteInstallNewtDescription": "在您的系统中运行 Newt",
"siteInstallKubernetesDocsDescription": "For more and up to date Kubernetes installation information, see <docsLink>docs.pangolin.net/manage/sites/install-kubernetes</docsLink>.",
"siteInstallAdvantechDocsDescription": "For Advantech modem installation instructions, see <docsLink>docs.pangolin.net/manage/sites/install-advantech</docsLink>.",
"WgConfiguration": "WireGuard 配置", "WgConfiguration": "WireGuard 配置",
"WgConfigurationDescription": "使用以下配置连接到网络", "WgConfigurationDescription": "使用以下配置连接到网络",
"operatingSystem": "操作系统", "operatingSystem": "操作系统",
@@ -202,8 +200,8 @@
"shareErrorSelectResource": "请选择一个资源", "shareErrorSelectResource": "请选择一个资源",
"proxyResourceTitle": "管理公共资源", "proxyResourceTitle": "管理公共资源",
"proxyResourceDescription": "创建和管理可通过 Web 浏览器公开访问的资源", "proxyResourceDescription": "创建和管理可通过 Web 浏览器公开访问的资源",
"publicResourcesBannerTitle": "Web-based Public Access", "publicResourcesBannerTitle": "基于Web的公共访问",
"publicResourcesBannerDescription": "Public resources are HTTPS proxies accessible to anyone on the internet through a web browser. Unlike private resources, they do not require client-side software and can include identity and context-aware access policies.", "publicResourcesBannerDescription": "公共资源是可以通过网络浏览器在互联网上任何人访问的HTTPS或TCP/UDP代理。与私人资源不同,它们不需要客户端软件,并且可以包含身份和上下文感知访问策略。",
"clientResourceTitle": "管理私有资源", "clientResourceTitle": "管理私有资源",
"clientResourceDescription": "创建和管理只能通过连接客户端访问的资源", "clientResourceDescription": "创建和管理只能通过连接客户端访问的资源",
"privateResourcesBannerTitle": "零信任的私人访问", "privateResourcesBannerTitle": "零信任的私人访问",
@@ -211,16 +209,15 @@
"resourcesSearch": "搜索资源...", "resourcesSearch": "搜索资源...",
"resourceAdd": "添加资源", "resourceAdd": "添加资源",
"resourceErrorDelte": "删除资源时出错", "resourceErrorDelte": "删除资源时出错",
"resourcePoliciesTitle": "Manage Public Resource Policies", "resourcePoliciesTitle": "管理资源策略",
"resourcePoliciesAttachedResourcesColumnTitle": "Resources", "resourcePoliciesAttachedResourcesColumnTitle": "附加资源",
"resourcePoliciesAttachedResources": "{count} 个资源", "resourcePoliciesAttachedResources": "{count} 个资源",
"resourcePoliciesAttachedResourcesCount": "{count, plural, one {# resource} other {# resources}}",
"resourcePoliciesAttachedResourcesEmpty": "没有资源", "resourcePoliciesAttachedResourcesEmpty": "没有资源",
"resourcePoliciesDescription": "Create and manage authentication policies to control access to your public resources", "resourcePoliciesDescription": "创建和管理身份验证策略以控制对资源的访问",
"resourcePoliciesSearch": "搜索策略……", "resourcePoliciesSearch": "搜索策略……",
"resourcePoliciesAdd": "添加策略", "resourcePoliciesAdd": "添加策略",
"resourcePoliciesDefaultBadgeText": "默认策略", "resourcePoliciesDefaultBadgeText": "默认策略",
"resourcePoliciesCreate": "Create Public Resource Policy", "resourcePoliciesCreate": "创建资源策略",
"resourcePoliciesCreateDescription": "按照以下步骤创建新策略", "resourcePoliciesCreateDescription": "按照以下步骤创建新策略",
"resourcePolicyName": "策略名称", "resourcePolicyName": "策略名称",
"resourcePolicyNameDescription": "给此策略命名,以便在您的资源中识别它", "resourcePolicyNameDescription": "给此策略命名,以便在您的资源中识别它",
@@ -314,7 +311,7 @@
"rules": "规则", "rules": "规则",
"resourceSettingDescription": "配置资源上的设置", "resourceSettingDescription": "配置资源上的设置",
"resourceSetting": "{resourceName} 设置", "resourceSetting": "{resourceName} 设置",
"resourcePolicySettingDescription": "Configure the settings on this public resource policy", "resourcePolicySettingDescription": "配置资源策略上的设置",
"resourcePolicySetting": "{policyName} 设置", "resourcePolicySetting": "{policyName} 设置",
"alwaysAllow": "旁路认证", "alwaysAllow": "旁路认证",
"alwaysDeny": "屏蔽访问", "alwaysDeny": "屏蔽访问",
@@ -756,11 +753,11 @@
"rulesErrorDuplicate": "复制规则", "rulesErrorDuplicate": "复制规则",
"rulesErrorDuplicateDescription": "带有这些设置的规则已存在", "rulesErrorDuplicateDescription": "带有这些设置的规则已存在",
"rulesErrorInvalidIpAddressRange": "无效的 CIDR", "rulesErrorInvalidIpAddressRange": "无效的 CIDR",
"rulesErrorInvalidIpAddressRangeDescription": "Enter a valid CIDR range (e.g., 10.0.0.0/8).", "rulesErrorInvalidIpAddressRangeDescription": "请输入一个有效的 CIDR 值",
"rulesErrorInvalidUrl": "Invalid path", "rulesErrorInvalidUrl": "无效的 URL 路径",
"rulesErrorInvalidUrlDescription": "Enter a valid URL path or pattern (e.g., /api/*).", "rulesErrorInvalidUrlDescription": "请输入一个有效的 URL 路径值",
"rulesErrorInvalidIpAddress": "Invalid IP address", "rulesErrorInvalidIpAddress": "无效的 IP",
"rulesErrorInvalidIpAddressDescription": "Enter a valid IPv4 or IPv6 address.", "rulesErrorInvalidIpAddressDescription": "请输入一个有效的IP地址",
"rulesErrorUpdate": "更新规则失败", "rulesErrorUpdate": "更新规则失败",
"rulesErrorUpdateDescription": "更新规则时出错", "rulesErrorUpdateDescription": "更新规则时出错",
"rulesUpdated": "启用规则", "rulesUpdated": "启用规则",
@@ -768,23 +765,15 @@
"rulesMatchIpAddressRangeDescription": "以 CIDR 格式输入地址(如:103.21.244.0/22)", "rulesMatchIpAddressRangeDescription": "以 CIDR 格式输入地址(如:103.21.244.0/22)",
"rulesMatchIpAddress": "输入IP地址(例如,103.21.244.12)", "rulesMatchIpAddress": "输入IP地址(例如,103.21.244.12)",
"rulesMatchUrl": "输入一个 URL 路径或模式(例如/api/v1/todos 或 /api/v1/*)", "rulesMatchUrl": "输入一个 URL 路径或模式(例如/api/v1/todos 或 /api/v1/*)",
"rulesErrorInvalidPriority": "Invalid priority", "rulesErrorInvalidPriority": "无效的优先级",
"rulesErrorInvalidPriorityDescription": "Enter a whole number of 1 or higher.", "rulesErrorInvalidPriorityDescription": "请输入一个有效的优先级",
"rulesErrorDuplicatePriority": "Duplicate priorities", "rulesErrorDuplicatePriority": "重复的优先级",
"rulesErrorDuplicatePriorityDescription": "Each rule must have a unique priority number.", "rulesErrorDuplicatePriorityDescription": "请输入唯一的优先级",
"rulesErrorValidation": "Invalid rules",
"rulesErrorValidationRuleDescription": "Rule {ruleNumber}: {message}",
"rulesErrorValueRequired": "Enter a value for this rule.",
"rulesErrorInvalidCountry": "Invalid country",
"rulesErrorInvalidCountryDescription": "Select a valid country.",
"rulesErrorInvalidAsn": "Invalid ASN",
"rulesErrorInvalidAsnDescription": "Enter a valid ASN (e.g., AS15169).",
"ruleUpdated": "规则已更新", "ruleUpdated": "规则已更新",
"ruleUpdatedDescription": "规则更新成功", "ruleUpdatedDescription": "规则更新成功",
"ruleErrorUpdate": "操作失败", "ruleErrorUpdate": "操作失败",
"ruleErrorUpdateDescription": "保存过程中发生错误", "ruleErrorUpdateDescription": "保存过程中发生错误",
"rulesPriority": "优先权", "rulesPriority": "优先权",
"rulesReorderDragHandle": "Drag to reorder rule priority",
"rulesAction": "行为", "rulesAction": "行为",
"rulesMatchType": "匹配类型", "rulesMatchType": "匹配类型",
"value": "值", "value": "值",
@@ -803,7 +792,7 @@
"rulesResource": "资源规则配置", "rulesResource": "资源规则配置",
"rulesResourceDescription": "配置规则来控制对资源的访问", "rulesResourceDescription": "配置规则来控制对资源的访问",
"ruleSubmit": "添加规则", "ruleSubmit": "添加规则",
"rulesNoOne": "No rules yet.", "rulesNoOne": "没有规则。使用表单添加规则。",
"rulesOrder": "规则按优先顺序评定。", "rulesOrder": "规则按优先顺序评定。",
"rulesSubmit": "保存规则", "rulesSubmit": "保存规则",
"policyErrorCreate": "创建策略时出错", "policyErrorCreate": "创建策略时出错",
@@ -814,44 +803,7 @@
"policyErrorUpdateMessageDescription": "发生意外错误", "policyErrorUpdateMessageDescription": "发生意外错误",
"policyCreatedSuccess": "资源策略创建成功", "policyCreatedSuccess": "资源策略创建成功",
"policyUpdatedSuccess": "资源策略更新成功", "policyUpdatedSuccess": "资源策略更新成功",
"authMethodsSave": "Save Settings", "authMethodsSave": "保存身份验证方法",
"policyAuthStackTitle": "Authentication",
"policyAuthStackDescription": "Control which authentication methods are required to access this resource",
"policyAuthOrLogicTitle": "Multiple authentication methods active",
"policyAuthOrLogicBanner": "Visitors may authenticate using any one of the active methods below. They do not need to complete all of them.",
"policyAuthMethodActive": "Active",
"policyAuthMethodOff": "Off",
"policyAuthSsoTitle": "Platform SSO",
"policyAuthSsoDescription": "Require sign-in through your organization's identity provider",
"policyAuthSsoSummary": "{idp} · {users} users, {roles} roles",
"policyAuthSsoDefaultIdp": "Default provider",
"policyAuthAddDefaultIdentityProvider": "Add Default Identity Provider",
"policyAuthOtherMethodsTitle": "Other Methods",
"policyAuthOtherMethodsDescription": "Optional methods visitors can use instead of or alongside platform SSO",
"policyAuthPasscodeTitle": "Passcode",
"policyAuthPasscodeDescription": "Require a shared alphanumeric passcode to access the resource",
"policyAuthPasscodeSummary": "Passcode set",
"policyAuthPincodeTitle": "PIN Code",
"policyAuthPincodeDescription": "A short numeric code required to access the resource",
"policyAuthPincodeSummary": "6-digit PIN set",
"policyAuthEmailTitle": "Email Whitelist",
"policyAuthEmailDescription": "Allow listed email addresses with one-time passwords",
"policyAuthEmailSummary": "{count} addresses allowed",
"policyAuthEmailOtpCallout": "Enabling email whitelist sends a one-time password to the visitor's email on login.",
"policyAuthHeaderAuthTitle": "Basic Header Auth",
"policyAuthHeaderAuthDescription": "Validate a custom HTTP header name and value on each request",
"policyAuthHeaderAuthSummary": "Header configured",
"policyAuthHeaderName": "Header name",
"policyAuthHeaderValue": "Expected value",
"policyAccessRulesTitle": "Access Rules",
"policyAccessRulesEnableDescription": "When enabled, rules are evaluated in descending order until one evaluates as true.",
"policyAccessRulesFirstMatch": "Rules are evaluated top to bottom. The first matching rule decides the outcome.",
"policyAccessRulesHowItWorks": "Rules match requests by path, IP address, location, or other criteria. Each rule applies an action: bypass authentication, block access, or pass to authentication. If no rule matches, traffic continues to authentication.",
"policyAccessRulesFallthroughOff": "When rules are disabled, all traffic passes through to authentication.",
"policyAccessRulesFallthroughOn": "When no rule matches, traffic passes through to authentication.",
"rulesPlaceholderCidr": "10.0.0.0/8",
"rulesPlaceholderPath": "/admin/*",
"rulesPlaceholderGeo": "RU, KP",
"rulesSave": "保存规则", "rulesSave": "保存规则",
"resourceErrorCreate": "创建资源时出错", "resourceErrorCreate": "创建资源时出错",
"resourceErrorCreateDescription": "创建资源时出错", "resourceErrorCreateDescription": "创建资源时出错",
@@ -1268,10 +1220,8 @@
"addLabels": "添加标签", "addLabels": "添加标签",
"siteLabelsTab": "标签", "siteLabelsTab": "标签",
"siteLabelsDescription": "管理与此网站相关的标签。", "siteLabelsDescription": "管理与此网站相关的标签。",
"labelsNotFound": "No labels found.", "labelsNotFound": "找不到标签",
"labelsEmptyCreateHint": "Start typing above to create a label.",
"labelSearch": "搜索标签", "labelSearch": "搜索标签",
"labelSearchOrCreate": "Search or create a label",
"accessLabelFilterCount": "{count, plural, other {# 标签}}", "accessLabelFilterCount": "{count, plural, other {# 标签}}",
"labelOverflowCount": "+{count, plural, other {# 标签}}", "labelOverflowCount": "+{count, plural, other {# 标签}}",
"accessLabelFilterClear": "清除标签过滤器", "accessLabelFilterClear": "清除标签过滤器",
@@ -1512,7 +1462,7 @@
"sidebarProxyResources": "公开的", "sidebarProxyResources": "公开的",
"sidebarClientResources": "非公开的", "sidebarClientResources": "非公开的",
"sidebarPolicies": "策略", "sidebarPolicies": "策略",
"sidebarResourcePolicies": "Public Resources", "sidebarResourcePolicies": "资源",
"sidebarAccessControl": "访问控制", "sidebarAccessControl": "访问控制",
"sidebarLogsAndAnalytics": "日志与分析", "sidebarLogsAndAnalytics": "日志与分析",
"sidebarTeam": "团队", "sidebarTeam": "团队",
@@ -1520,7 +1470,7 @@
"sidebarAdmin": "管理员", "sidebarAdmin": "管理员",
"sidebarInvitations": "邀请", "sidebarInvitations": "邀请",
"sidebarRoles": "角色", "sidebarRoles": "角色",
"sidebarShareableLinks": "Share Links", "sidebarShareableLinks": "链接",
"sidebarApiKeys": "API密钥", "sidebarApiKeys": "API密钥",
"sidebarProvisioning": "置备中", "sidebarProvisioning": "置备中",
"sidebarSettings": "设置", "sidebarSettings": "设置",
@@ -1697,7 +1647,7 @@
"standaloneHcFilterResourceIdFallback": "资源 {id}", "standaloneHcFilterResourceIdFallback": "资源 {id}",
"blueprints": "蓝图", "blueprints": "蓝图",
"blueprintsLog": "蓝图日志", "blueprintsLog": "蓝图日志",
"blueprintsDescription": "View past blueprint applications and their results or apply a new blueprint", "blueprintsDescription": "查看过去的蓝图应用及其结果",
"blueprintAdd": "添加蓝图", "blueprintAdd": "添加蓝图",
"blueprintGoBack": "查看所有蓝图", "blueprintGoBack": "查看所有蓝图",
"blueprintCreate": "创建蓝图", "blueprintCreate": "创建蓝图",
@@ -2077,13 +2027,13 @@
"healthCheckUnknown": "未知", "healthCheckUnknown": "未知",
"healthCheck": "健康检查", "healthCheck": "健康检查",
"configureHealthCheck": "配置健康检查", "configureHealthCheck": "配置健康检查",
"configureHealthCheckDescription": "Set up monitoring for your resource to ensure it is always available", "configureHealthCheckDescription": "为 {target} 设置健康监控",
"enableHealthChecks": "启用健康检查", "enableHealthChecks": "启用健康检查",
"healthCheckDisabledStateDescription": "禁用后,站点不会进行健康检查,状态将被视为未知。", "healthCheckDisabledStateDescription": "禁用后,站点不会进行健康检查,状态将被视为未知。",
"enableHealthChecksDescription": "监视此目标的健康状况。如果需要,您可以监视一个不同的终点。", "enableHealthChecksDescription": "监视此目标的健康状况。如果需要,您可以监视一个不同的终点。",
"healthScheme": "方法", "healthScheme": "方法",
"healthSelectScheme": "选择方法", "healthSelectScheme": "选择方法",
"healthCheckPortInvalid": "Port must be between 1 and 65535", "healthCheckPortInvalid": "健康检查端口必须介于 1 到 65535 之间",
"healthCheckPath": "路径", "healthCheckPath": "路径",
"healthHostname": "IP / 主机", "healthHostname": "IP / 主机",
"healthPort": "端口", "healthPort": "端口",
@@ -2096,7 +2046,6 @@
"requireDeviceApproval": "需要设备批准", "requireDeviceApproval": "需要设备批准",
"requireDeviceApprovalDescription": "具有此角色的用户需要管理员批准的新设备才能连接和访问资源。", "requireDeviceApprovalDescription": "具有此角色的用户需要管理员批准的新设备才能连接和访问资源。",
"sshSettings": "SSH 设置", "sshSettings": "SSH 设置",
"sshAccess": "SSH Access",
"rdpSettings": "RDP 设置", "rdpSettings": "RDP 设置",
"vncSettings": "VNC 设置", "vncSettings": "VNC 设置",
"sshServer": "SSH 服务器", "sshServer": "SSH 服务器",
@@ -2123,13 +2072,8 @@
"sshDaemonDisclaimer": "在完成本设置之前,请确保您的目标主机已经正确配置以运行身份验证守护程序,否则配置将失败。", "sshDaemonDisclaimer": "在完成本设置之前,请确保您的目标主机已经正确配置以运行身份验证守护程序,否则配置将失败。",
"sshDaemonPort": "守护程序端口", "sshDaemonPort": "守护程序端口",
"sshServerDestination": "服务器目标", "sshServerDestination": "服务器目标",
"sshServerDestinationDescription": "Configure the destination of the SSH server", "sshServerDestinationDescription": "配置 SSH 服务器的目标和端口",
"destination": "目标", "destination": "目标",
"destinationRequired": "Destination is required.",
"domainRequired": "Domain is required.",
"proxyPortRequired": "Port is required.",
"invalidPathConfiguration": "Invalid path configuration.",
"invalidRewritePathConfiguration": "Invalid rewrite path configuration.",
"bgTargetMultiSiteDisclaimer": "选择多个站点可实现高可用性的弹性路由和故障转移。", "bgTargetMultiSiteDisclaimer": "选择多个站点可实现高可用性的弹性路由和故障转移。",
"roleAllowSsh": "允许 SSH", "roleAllowSsh": "允许 SSH",
"roleAllowSshAllow": "允许", "roleAllowSshAllow": "允许",
@@ -3090,7 +3034,7 @@
"enterConfirmation": "输入确认", "enterConfirmation": "输入确认",
"blueprintViewDetails": "详细信息", "blueprintViewDetails": "详细信息",
"defaultIdentityProvider": "默认身份提供商", "defaultIdentityProvider": "默认身份提供商",
"defaultIdentityProviderDescription": "The user will be automatically redirected to this identity provider for authentication.", "defaultIdentityProviderDescription": "当选择默认身份提供商时,用户将自动重定向到提供商进行身份验证。",
"editInternalResourceDialogNetworkSettings": "网络设置", "editInternalResourceDialogNetworkSettings": "网络设置",
"editInternalResourceDialogAccessPolicy": "访问策略", "editInternalResourceDialogAccessPolicy": "访问策略",
"editInternalResourceDialogAddRoles": "添加角色", "editInternalResourceDialogAddRoles": "添加角色",
@@ -3402,8 +3346,6 @@
"idpUnassociateQuestion": "您确定要将此身份提供者从此组织中取消关联吗?", "idpUnassociateQuestion": "您确定要将此身份提供者从此组织中取消关联吗?",
"idpUnassociateDescription": "与此身份提供者关联的所有用户将从该组织中移除,但身份提供者仍会继续存在于关联的其他组织中。", "idpUnassociateDescription": "与此身份提供者关联的所有用户将从该组织中移除,但身份提供者仍会继续存在于关联的其他组织中。",
"idpUnassociateConfirm": "确认取消关联身份提供者", "idpUnassociateConfirm": "确认取消关联身份提供者",
"idpConfirmDeleteAndRemoveMeFromOrg": "DELETE AND REMOVE ME FROM ORG",
"idpUnassociateAndRemoveMeFromOrg": "UNASSOCIATE AND REMOVE ME FROM ORG",
"idpUnassociateWarning": "此操作无法对该组织撤销。", "idpUnassociateWarning": "此操作无法对该组织撤销。",
"idpUnassociatedDescription": "身份提供者已成功从该组织中取消关联", "idpUnassociatedDescription": "身份提供者已成功从该组织中取消关联",
"idpUnassociateMenu": "取消关联", "idpUnassociateMenu": "取消关联",
@@ -3510,43 +3452,5 @@
"sshErrorNoTarget": "未指定目标", "sshErrorNoTarget": "未指定目标",
"sshErrorWebSocket": "WebSocket 连接失败", "sshErrorWebSocket": "WebSocket 连接失败",
"sshErrorAuthFailed": "身份验证失败", "sshErrorAuthFailed": "身份验证失败",
"sshErrorConnectionClosed": "认证完成前连接已关闭", "sshErrorConnectionClosed": "认证完成前连接已关闭"
"sitePangolinSshDescription": "Allow SSH access to resources on this site. This can be changed later.",
"browserGatewayNoResourceForDomain": "No resource found for this domain",
"browserGatewayNoTarget": "No target",
"browserGatewayConnect": "Connect",
"browserGatewayCtrlAltDel": "Ctrl+Alt+Del",
"sshErrorSignKeyFailed": "Failed to sign SSH key for PAM push authentication. Did you sign in as a user?",
"sshTerminalError": "Error: {error}",
"sshConnectionClosedCode": "Connection closed (code {code})",
"sshPrivateKeyPlaceholder": "-----BEGIN OPENSSH PRIVATE KEY-----",
"sshPrivateKeyRequired": "Private key is required",
"vncTitle": "VNC",
"vncSignInDescription": "Enter your VNC password to connect",
"vncPasswordOptional": "Password (optional)",
"vncNoResourceTarget": "No resource target is available",
"vncFailedToLoadNovnc": "Failed to load noVNC",
"vncAuthFailedStatus": "Status {status}",
"vncPasteClipboard": "Paste clipboard",
"rdpTitle": "RDP",
"rdpSignInTitle": "Sign in to Remote Desktop",
"rdpSignInDescription": "Enter Windows credentials to connect",
"rdpLoadingModule": "Loading module...",
"rdpFailedToLoadModule": "Failed to load RDP module",
"rdpNotReady": "Not ready",
"rdpModuleInitializing": "RDP module is still initializing",
"rdpDownloadingFiles": "Downloading {count} file(s) from remote…",
"rdpDownloadFailed": "Download failed: {fileName}",
"rdpUploaded": "Uploaded: {fileName}",
"rdpNoConnectionTarget": "No connection target available",
"rdpConnectionFailed": "Connection failed",
"rdpFit": "Fit",
"rdpFull": "Full",
"rdpReal": "Real",
"rdpMeta": "Meta",
"rdpUploadFiles": "Upload files",
"rdpFilesReadyToPaste": "Files ready to paste",
"rdpFilesReadyToPasteDescription": "{count} file(s) copied to remote clipboard — press Ctrl+V on the remote desktop to paste.",
"rdpUploadFailed": "Upload failed",
"rdpUnicodeKeyboardMode": "Unicode keyboard mode"
} }
+2 -2
View File
@@ -152,8 +152,8 @@
"shareErrorSelectResource": "請選擇一個資源", "shareErrorSelectResource": "請選擇一個資源",
"proxyResourceTitle": "管理公開資源", "proxyResourceTitle": "管理公開資源",
"proxyResourceDescription": "建立和管理可透過網頁瀏覽器公開存取的資源", "proxyResourceDescription": "建立和管理可透過網頁瀏覽器公開存取的資源",
"proxyResourcesBannerTitle": "基於網頁的公開存取", "publicResourcesBannerTitle": "基於網頁的公開存取",
"proxyResourcesBannerDescription": "公開資源是任何人都可以透過網頁瀏覽器存取的 HTTPS 或 TCP/UDP 代理。與私有資源不同,它們不需要客戶端軟體,並且可以包含基於身份和情境感知的存取策略。", "publicResourcesBannerDescription": "公開資源是任何人都可以透過網頁瀏覽器存取的 HTTPS 或 TCP/UDP 代理。與私有資源不同,它們不需要客戶端軟體,並且可以包含基於身份和情境感知的存取策略。",
"clientResourceTitle": "管理私有資源", "clientResourceTitle": "管理私有資源",
"clientResourceDescription": "建立和管理只能透過已連接的客戶端存取的資源", "clientResourceDescription": "建立和管理只能透過已連接的客戶端存取的資源",
"privateResourcesBannerTitle": "零信任私有存取", "privateResourcesBannerTitle": "零信任私有存取",
+1 -1
View File
@@ -87,7 +87,7 @@ function createDb() {
export const db = createDb(); export const db = createDb();
export default db; export default db;
export const primaryDb = db.$primary as typeof db; // is this typeof a problem - techincally they are different types export const primaryDb = db.$primary as typeof db; // is this typeof a problem - technically they are different types
export type Transaction = Parameters< export type Transaction = Parameters<
Parameters<(typeof db)["transaction"]>[0] Parameters<(typeof db)["transaction"]>[0]
>[0]; >[0];
+3 -4
View File
@@ -2,7 +2,7 @@ import { drizzle as DrizzlePostgres } from "drizzle-orm/node-postgres";
import { readConfigFile } from "@server/lib/readConfigFile"; import { readConfigFile } from "@server/lib/readConfigFile";
import { withReplicas } from "drizzle-orm/pg-core"; import { withReplicas } from "drizzle-orm/pg-core";
import { build } from "@server/build"; import { build } from "@server/build";
import { db as mainDb, primaryDb as mainPrimaryDb } from "./driver"; import { db as mainDb } from "./driver";
import { createPool } from "./poolConfig"; import { createPool } from "./poolConfig";
function createLogsDb() { function createLogsDb() {
@@ -63,8 +63,7 @@ function createLogsDb() {
}) })
); );
} else { } else {
const maxReplicaConnections = const maxReplicaConnections = poolConfig?.max_replica_connections || 20;
poolConfig?.max_replica_connections || 20;
for (const conn of replicaConnections) { for (const conn of replicaConnections) {
const replicaPool = createPool( const replicaPool = createPool(
conn.connection_string, conn.connection_string,
@@ -91,4 +90,4 @@ function createLogsDb() {
export const logsDb = createLogsDb(); export const logsDb = createLogsDb();
export default logsDb; export default logsDb;
export const primaryLogsDb = logsDb.$primary; export const primaryLogsDb = logsDb.$primary;
+3 -4
View File
@@ -1,5 +1,4 @@
import { Pool, PoolConfig } from "pg"; import { Pool, PoolConfig } from "pg";
import logger from "@server/logger";
export function createPoolConfig( export function createPoolConfig(
connectionString: string, connectionString: string,
@@ -27,7 +26,7 @@ export function attachPoolErrorHandlers(pool: Pool, label: string): void {
pool.on("error", (err) => { pool.on("error", (err) => {
// This catches errors on idle clients in the pool. Without this // This catches errors on idle clients in the pool. Without this
// handler an unexpected disconnect would crash the process. // handler an unexpected disconnect would crash the process.
logger.error( console.error(
`Unexpected error on idle ${label} database client: ${err.message}` `Unexpected error on idle ${label} database client: ${err.message}`
); );
}); });
@@ -36,7 +35,7 @@ export function attachPoolErrorHandlers(pool: Pool, label: string): void {
// Set a statement timeout on every new connection so a single slow // Set a statement timeout on every new connection so a single slow
// query can't block the pool forever // query can't block the pool forever
client.query("SET statement_timeout = '30s'").catch((err: Error) => { client.query("SET statement_timeout = '30s'").catch((err: Error) => {
logger.warn( console.warn(
`Failed to set statement_timeout on ${label} client: ${err.message}` `Failed to set statement_timeout on ${label} client: ${err.message}`
); );
}); });
@@ -60,4 +59,4 @@ export function createPool(
); );
attachPoolErrorHandlers(pool, label); attachPoolErrorHandlers(pool, label);
return pool; return pool;
} }
-21
View File
@@ -580,24 +580,6 @@ export const trialNotifications = pgTable("trialNotifications", {
sentAt: bigint("sentAt", { mode: "number" }).notNull() 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 Approval = InferSelectModel<typeof approvals>;
export type Limit = InferSelectModel<typeof limits>; export type Limit = InferSelectModel<typeof limits>;
export type Account = InferSelectModel<typeof account>; export type Account = InferSelectModel<typeof account>;
@@ -645,6 +627,3 @@ export type AlertEmailRecipients = InferSelectModel<
>; >;
export type AlertWebhookActions = InferSelectModel<typeof alertWebhookActions>; export type AlertWebhookActions = InferSelectModel<typeof alertWebhookActions>;
export type TrialNotification = InferSelectModel<typeof trialNotifications>; export type TrialNotification = InferSelectModel<typeof trialNotifications>;
export type BrowserGatewayTarget = InferSelectModel<
typeof browserGatewayTarget
>;
+12 -7
View File
@@ -147,12 +147,10 @@ export const resources = pgTable("resources", {
}), }),
ssl: boolean("ssl").notNull().default(false), ssl: boolean("ssl").notNull().default(false),
blockAccess: boolean("blockAccess").notNull().default(false), blockAccess: boolean("blockAccess").notNull().default(false),
sso: boolean("sso").notNull().default(true),
proxyPort: integer("proxyPort"), proxyPort: integer("proxyPort"),
emailWhitelistEnabled: boolean("emailWhitelistEnabled") sso: boolean("sso"),
.notNull() emailWhitelistEnabled: boolean("emailWhitelistEnabled"),
.default(false), applyRules: boolean("applyRules"),
applyRules: boolean("applyRules").notNull().default(false),
enabled: boolean("enabled").notNull().default(true), enabled: boolean("enabled").notNull().default(true),
stickySession: boolean("stickySession").notNull().default(false), stickySession: boolean("stickySession").notNull().default(false),
tlsServerName: varchar("tlsServerName"), tlsServerName: varchar("tlsServerName"),
@@ -290,7 +288,12 @@ export const targets = pgTable("targets", {
pathMatchType: text("pathMatchType"), // exact, prefix, regex pathMatchType: text("pathMatchType"), // exact, prefix, regex
rewritePath: text("rewritePath"), // if set, rewrites the path to this value before sending to the target rewritePath: text("rewritePath"), // if set, rewrites the path to this value before sending to the target
rewritePathType: text("rewritePathType"), // exact, prefix, regex, stripPrefix rewritePathType: text("rewritePathType"), // exact, prefix, regex, stripPrefix
priority: integer("priority").notNull().default(100) priority: integer("priority").notNull().default(100),
mode: varchar("mode")
.$type<"http" | "tcp" | "udp" | "ssh" | "rdp" | "vnc">()
.notNull()
.default("http"),
authToken: varchar("authToken")
}); });
export const targetHealthCheck = pgTable("targetHealthCheck", { export const targetHealthCheck = pgTable("targetHealthCheck", {
@@ -886,7 +889,9 @@ export const resourcePolicyRules = pgTable("resourcePolicyRules", {
enabled: boolean("enabled").notNull().default(true), enabled: boolean("enabled").notNull().default(true),
priority: integer("priority").notNull(), priority: integer("priority").notNull(),
action: varchar("action").$type<"ACCEPT" | "DROP" | "PASS">().notNull(), action: varchar("action").$type<"ACCEPT" | "DROP" | "PASS">().notNull(),
match: varchar("match").$type<"CIDR" | "PATH" | "IP">().notNull(), match: varchar("match")
.$type<"CIDR" | "PATH" | "IP" | "COUNTRY" | "ASN" | "REGION">()
.notNull(),
value: varchar("value").notNull() value: varchar("value").notNull()
}); });
+3 -3
View File
@@ -45,9 +45,9 @@ export type ResourceWithAuth = {
password: ResourcePassword | ResourcePolicyPassword | null; password: ResourcePassword | ResourcePolicyPassword | null;
headerAuth: ResourceHeaderAuth | ResourcePolicyHeaderAuth | null; headerAuth: ResourceHeaderAuth | ResourcePolicyHeaderAuth | null;
headerAuthExtendedCompatibility: ResourceHeaderAuthExtendedCompatibility | null; headerAuthExtendedCompatibility: ResourceHeaderAuthExtendedCompatibility | null;
applyRules: boolean; applyRules: boolean | null;
sso: boolean; sso: boolean | null;
emailWhitelistEnabled: boolean; emailWhitelistEnabled: boolean | null;
org: Org; org: Org;
}; };
-23
View File
@@ -588,26 +588,6 @@ export const trialNotifications = sqliteTable("trialNotifications", {
sentAt: integer("sentAt").notNull() 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 Approval = InferSelectModel<typeof approvals>;
export type Limit = InferSelectModel<typeof limits>; export type Limit = InferSelectModel<typeof limits>;
export type Account = InferSelectModel<typeof account>; export type Account = InferSelectModel<typeof account>;
@@ -647,6 +627,3 @@ export type AlertEmailAction = InferSelectModel<typeof alertEmailActions>;
export type AlertEmailRecipient = InferSelectModel<typeof alertEmailRecipients>; export type AlertEmailRecipient = InferSelectModel<typeof alertEmailRecipients>;
export type AlertWebhookAction = InferSelectModel<typeof alertWebhookActions>; export type AlertWebhookAction = InferSelectModel<typeof alertWebhookActions>;
export type TrialNotification = InferSelectModel<typeof trialNotifications>; export type TrialNotification = InferSelectModel<typeof trialNotifications>;
export type BrowserGatewayTarget = InferSelectModel<
typeof browserGatewayTarget
>;
+14 -9
View File
@@ -165,14 +165,12 @@ export const resources = sqliteTable("resources", {
blockAccess: integer("blockAccess", { mode: "boolean" }) blockAccess: integer("blockAccess", { mode: "boolean" })
.notNull() .notNull()
.default(false), .default(false),
sso: integer("sso", { mode: "boolean" }).notNull().default(true),
proxyPort: integer("proxyPort"), proxyPort: integer("proxyPort"),
emailWhitelistEnabled: integer("emailWhitelistEnabled", { mode: "boolean" }) sso: integer("sso", { mode: "boolean" }),
.notNull() emailWhitelistEnabled: integer("emailWhitelistEnabled", {
.default(false), mode: "boolean"
applyRules: integer("applyRules", { mode: "boolean" }) }),
.notNull() applyRules: integer("applyRules", { mode: "boolean" }),
.default(false),
enabled: integer("enabled", { mode: "boolean" }).notNull().default(true), enabled: integer("enabled", { mode: "boolean" }).notNull().default(true),
stickySession: integer("stickySession", { mode: "boolean" }) stickySession: integer("stickySession", { mode: "boolean" })
.notNull() .notNull()
@@ -322,7 +320,12 @@ export const targets = sqliteTable("targets", {
pathMatchType: text("pathMatchType"), // exact, prefix, regex pathMatchType: text("pathMatchType"), // exact, prefix, regex
rewritePath: text("rewritePath"), // if set, rewrites the path to this value before sending to the target rewritePath: text("rewritePath"), // if set, rewrites the path to this value before sending to the target
rewritePathType: text("rewritePathType"), // exact, prefix, regex, stripPrefix rewritePathType: text("rewritePathType"), // exact, prefix, regex, stripPrefix
priority: integer("priority").notNull().default(100) priority: integer("priority").notNull().default(100),
mode: text("mode")
.$type<"http" | "tcp" | "udp" | "ssh" | "rdp" | "vnc">()
.notNull()
.default("http"),
authToken: text("authToken")
}); });
export const targetHealthCheck = sqliteTable("targetHealthCheck", { export const targetHealthCheck = sqliteTable("targetHealthCheck", {
@@ -1248,7 +1251,9 @@ export const resourcePolicyRules = sqliteTable("resourcePolicyRules", {
enabled: integer("enabled", { mode: "boolean" }).notNull().default(true), enabled: integer("enabled", { mode: "boolean" }).notNull().default(true),
priority: integer("priority").notNull(), priority: integer("priority").notNull(),
action: text("action").$type<"ACCEPT" | "DROP" | "PASS">().notNull(), action: text("action").$type<"ACCEPT" | "DROP" | "PASS">().notNull(),
match: text("match").$type<"CIDR" | "PATH" | "IP">().notNull(), match: text("match")
.$type<"CIDR" | "PATH" | "IP" | "COUNTRY" | "ASN" | "REGION">()
.notNull(),
value: text("value").notNull() value: text("value").notNull()
}); });
+3 -1
View File
@@ -157,7 +157,9 @@ function getOpenApiDocumentation() {
content: { content: {
"application/json": { "application/json": {
schema: z.object({ schema: z.object({
data: z.unknown().nullable(), data: z
.record(z.string(), z.any())
.nullable(),
success: z.boolean(), success: z.boolean(),
error: z.boolean(), error: z.boolean(),
message: z.string(), message: z.string(),
+3 -13
View File
@@ -31,7 +31,7 @@ export enum TierFeature {
} }
export const tierMatrix: Record<TierFeature, Tier[]> = { export const tierMatrix: Record<TierFeature, Tier[]> = {
[TierFeature.Labels]: ["tier2", "tier3", "enterprise"], [TierFeature.Labels]: ["tier1", "tier2", "tier3", "enterprise"],
[TierFeature.OrgOidc]: ["tier1", "tier2", "tier3", "enterprise"], [TierFeature.OrgOidc]: ["tier1", "tier2", "tier3", "enterprise"],
[TierFeature.LoginPageDomain]: ["tier1", "tier2", "tier3", "enterprise"], [TierFeature.LoginPageDomain]: ["tier1", "tier2", "tier3", "enterprise"],
[TierFeature.DeviceApprovals]: ["tier1", "tier3", "enterprise"], [TierFeature.DeviceApprovals]: ["tier1", "tier3", "enterprise"],
@@ -71,16 +71,6 @@ export const tierMatrix: Record<TierFeature, Tier[]> = {
[TierFeature.WildcardSubdomain]: ["tier1", "tier2", "tier3", "enterprise"], [TierFeature.WildcardSubdomain]: ["tier1", "tier2", "tier3", "enterprise"],
[TierFeature.NewtAutoUpdate]: ["tier1", "tier2", "tier3", "enterprise"], [TierFeature.NewtAutoUpdate]: ["tier1", "tier2", "tier3", "enterprise"],
[TierFeature.ResourcePolicies]: ["tier3", "enterprise"], [TierFeature.ResourcePolicies]: ["tier3", "enterprise"],
[TierFeature.AdvancedPublicResources]: [ [TierFeature.AdvancedPublicResources]: ["tier3", "enterprise"],
"tier1", [TierFeature.AdvancedPrivateResources]: ["tier3", "enterprise"]
"tier2",
"tier3",
"enterprise"
],
[TierFeature.AdvancedPrivateResources]: [
"tier1",
"tier2",
"tier3",
"enterprise"
]
}; };
+37 -14
View File
@@ -10,16 +10,23 @@ import {
clientSiteResources clientSiteResources
} from "@server/db"; } from "@server/db";
import { Config, ConfigSchema } from "./types"; import { Config, ConfigSchema } from "./types";
import { ProxyResourcesResults, updateProxyResources } from "./proxyResources"; import {
PublicResourcesResults,
updatePublicResources
} from "./publicResources";
import { fromError } from "zod-validation-error"; import { fromError } from "zod-validation-error";
import logger from "@server/logger"; import logger from "@server/logger";
import { sites } from "@server/db"; import { sites } from "@server/db";
import { eq, and, isNotNull } from "drizzle-orm"; import { eq, and, isNotNull } from "drizzle-orm";
import { addTargets as addProxyTargets } from "@server/routers/newt/targets"; import {
addTargets as addProxyTargets,
sendBrowserGatewayTargets
} from "@server/routers/newt/targets";
import { import {
ClientResourcesResults, ClientResourcesResults,
updateClientResources updatePrivateResources
} from "./clientResources"; } from "./privateResources";
import { updateResourcePolicies } from "./resourcePolicies";
import { BlueprintSource } from "@server/routers/blueprints/types"; import { BlueprintSource } from "@server/routers/blueprints/types";
import { stringify as stringifyYaml } from "yaml"; import { stringify as stringifyYaml } from "yaml";
import { generateName } from "@server/db/names"; import { generateName } from "@server/db/names";
@@ -53,16 +60,18 @@ export async function applyBlueprint({
let error: any | null = null; let error: any | null = null;
try { try {
let proxyResourcesResults: ProxyResourcesResults = []; let proxyResourcesResults: PublicResourcesResults = [];
let clientResourcesResults: ClientResourcesResults = []; let clientResourcesResults: ClientResourcesResults = [];
await db.transaction(async (trx) => { await db.transaction(async (trx) => {
proxyResourcesResults = await updateProxyResources( await updateResourcePolicies(orgId, config, trx);
proxyResourcesResults = await updatePublicResources(
orgId, orgId,
config, config,
trx, trx,
siteId siteId
); );
clientResourcesResults = await updateClientResources( clientResourcesResults = await updatePrivateResources(
orgId, orgId,
config, config,
trx, trx,
@@ -101,13 +110,27 @@ export async function applyBlueprint({
(hc) => hc.targetId === target.targetId (hc) => hc.targetId === target.targetId
); );
await addProxyTargets( if (["http", "tcp", "udp"].includes(target.mode)) {
site.newt.newtId, await addProxyTargets(
[target], site.newt.newtId,
matchingHealthcheck ? [matchingHealthcheck] : [], [target],
result.proxyResource.mode === "udp" ? "udp" : "tcp", matchingHealthcheck
site.newt.version ? [matchingHealthcheck]
); : [],
result.proxyResource.mode === "udp"
? "udp"
: "tcp",
site.newt.version
);
} else if (
["ssh", "rdp", "vnc"].includes(target.mode)
) {
await sendBrowserGatewayTargets(
site.newt.newtId,
[target],
site.newt.version
);
}
} }
} }
} }
@@ -23,6 +23,8 @@ import logger from "@server/logger";
import { defaultRoleAllowedActions } from "@server/routers/role/createRole"; import { defaultRoleAllowedActions } from "@server/routers/role/createRole";
import { getNextAvailableAliasAddress } from "../ip"; import { getNextAvailableAliasAddress } from "../ip";
import { createCertificate } from "#dynamic/routers/certificates/createCertificate"; import { createCertificate } from "#dynamic/routers/certificates/createCertificate";
import { isLicensedOrSubscribed } from "#dynamic/lib/isLicencedOrSubscribed";
import { tierMatrix } from "../billing/tierMatrix";
async function getDomainForSiteResource( async function getDomainForSiteResource(
siteResourceId: number | undefined, siteResourceId: number | undefined,
@@ -103,7 +105,7 @@ export type ClientResourcesResults = {
oldSites: { siteId: number }[]; oldSites: { siteId: number }[];
}[]; }[];
export async function updateClientResources( export async function updatePrivateResources(
orgId: string, orgId: string,
config: Config, config: Config,
trx: Transaction, trx: Transaction,
@@ -114,6 +116,30 @@ export async function updateClientResources(
for (const [resourceNiceId, resourceData] of Object.entries( for (const [resourceNiceId, resourceData] of Object.entries(
config["client-resources"] config["client-resources"]
)) { )) {
if (resourceData.mode === "http") {
const hasHttpFeature = await isLicensedOrSubscribed(
orgId,
tierMatrix.advancedPrivateResources
);
if (!hasHttpFeature) {
throw new Error(
"HTTP private resources are not included in your current plan. Please upgrade."
);
}
}
if (resourceData.mode === "ssh") {
const hasSshFeature = await isLicensedOrSubscribed(
orgId,
tierMatrix.advancedPrivateResources
);
if (!hasSshFeature) {
throw new Error(
"SSH private resources are not included in your current plan. Please upgrade."
);
}
}
const [existingResource] = await trx const [existingResource] = await trx
.select() .select()
.from(siteResources) .from(siteResources)
@@ -366,7 +392,9 @@ export async function updateClientResources(
})) }))
); );
existingRoles.push(created); existingRoles.push(created);
logger.info(`Auto-created role "${name}" in org ${orgId} from blueprint`); logger.info(
`Auto-created role "${name}" in org ${orgId} from blueprint`
);
} }
const roleIds = existingRoles.map((role) => role.roleId); const roleIds = existingRoles.map((role) => role.roleId);
@@ -387,7 +415,11 @@ export async function updateClientResources(
} else { } else {
let aliasAddress: string | null = null; let aliasAddress: string | null = null;
let releaseAliasLock: (() => Promise<void>) | null = null; let releaseAliasLock: (() => Promise<void>) | null = null;
if (resourceData.mode === "host" || resourceData.mode === "http") { if (
resourceData.mode === "host" ||
resourceData.mode === "http" ||
resourceData.mode === "ssh"
) {
const { value, release } = await getNextAvailableAliasAddress( const { value, release } = await getNextAvailableAliasAddress(
orgId, orgId,
trx trx
@@ -510,7 +542,9 @@ export async function updateClientResources(
})) }))
); );
existingRoles.push(created); existingRoles.push(created);
logger.info(`Auto-created role "${name}" in org ${orgId} from blueprint`); logger.info(
`Auto-created role "${name}" in org ${orgId} from blueprint`
);
} }
const roleIds = existingRoles.map((role) => role.roleId); const roleIds = existingRoles.map((role) => role.roleId);
@@ -47,20 +47,24 @@ import { isLicensedOrSubscribed } from "#dynamic/lib/isLicencedOrSubscribed";
import { fireHealthCheckUnknownAlert } from "@server/lib/alerts"; import { fireHealthCheckUnknownAlert } from "@server/lib/alerts";
import { tierMatrix } from "../billing/tierMatrix"; import { tierMatrix } from "../billing/tierMatrix";
import { defaultRoleAllowedActions } from "@server/routers/role/createRole"; import { defaultRoleAllowedActions } from "@server/routers/role/createRole";
import { build } from "@server/build";
import { encrypt } from "@server/lib/crypto";
import { generateId } from "@server/auth/sessions/app";
import serverConfig from "@server/lib/config";
export type ProxyResourcesResults = { export type PublicResourcesResults = {
proxyResource: Resource; proxyResource: Resource;
targetsToUpdate: Target[]; targetsToUpdate: Target[];
healthchecksToUpdate: TargetHealthCheck[]; healthchecksToUpdate: TargetHealthCheck[];
}[]; }[];
export async function updateProxyResources( export async function updatePublicResources(
orgId: string, orgId: string,
config: Config, config: Config,
trx: Transaction, trx: Transaction,
siteId?: number siteId?: number
): Promise<ProxyResourcesResults> { ): Promise<PublicResourcesResults> {
const results: ProxyResourcesResults = []; const results: PublicResourcesResults = [];
for (const [resourceNiceId, resourceData] of Object.entries( for (const [resourceNiceId, resourceData] of Object.entries(
config["proxy-resources"] config["proxy-resources"]
@@ -79,7 +83,7 @@ export async function updateProxyResources(
if (targetSiteId) { if (targetSiteId) {
// Look up site by niceId // Look up site by niceId
[site] = await trx [site] = await trx
.select({ siteId: sites.siteId }) .select({ siteId: sites.siteId, type: sites.type })
.from(sites) .from(sites)
.where( .where(
and( and(
@@ -91,7 +95,7 @@ export async function updateProxyResources(
} else if (siteId) { } else if (siteId) {
// Use the provided siteId directly, but verify it belongs to the org // Use the provided siteId directly, but verify it belongs to the org
[site] = await trx [site] = await trx
.select({ siteId: sites.siteId }) .select({ siteId: sites.siteId, type: sites.type })
.from(sites) .from(sites)
.where( .where(
and(eq(sites.siteId, siteId), eq(sites.orgId, orgId)) and(eq(sites.siteId, siteId), eq(sites.orgId, orgId))
@@ -118,6 +122,15 @@ export async function updateProxyResources(
internalPortToCreate = targetData["internal-port"]; internalPortToCreate = targetData["internal-port"];
} }
let authToken: string | undefined;
if (site.type !== "local") {
const plainToken = generateId(48);
authToken = encrypt(
plainToken,
serverConfig.getRawConfig().server.secret!
);
}
// Create target // Create target
const [newTarget] = await trx const [newTarget] = await trx
.insert(targets) .insert(targets)
@@ -125,10 +138,12 @@ export async function updateProxyResources(
resourceId: resourceId, resourceId: resourceId,
siteId: site.siteId, siteId: site.siteId,
ip: targetData.hostname, ip: targetData.hostname,
mode: resourceData.mode as Target["mode"],
method: targetData.method, method: targetData.method,
port: targetData.port, port: targetData.port,
enabled: targetData.enabled, enabled: targetData.enabled,
internalPort: internalPortToCreate, internalPort: internalPortToCreate,
authToken: authToken,
path: targetData.path, path: targetData.path,
pathMatchType: targetData["path-match"], pathMatchType: targetData["path-match"],
rewritePath: rewritePath:
@@ -222,17 +237,59 @@ export async function updateProxyResources(
headers = JSON.stringify(resourceData.headers); headers = JSON.stringify(resourceData.headers);
} }
if (["ssh", "rdp", "vnc"].includes(resourceData.mode || "")) {
const isLicensed = await isLicensedOrSubscribed(
orgId,
tierMatrix.advancedPublicResources
);
if (!isLicensed) {
throw new Error(
"Your current subscription does not support browser gateway resources. Please upgrade to access this feature."
);
}
}
if (resourceData.policy) {
const isLicensed = await isLicensedOrSubscribed(
orgId,
tierMatrix.resourcePolicies
);
if (!isLicensed) {
throw new Error(
"Your current subscription does not support shared resource policies. Please upgrade to access this feature."
);
}
}
if (existingResource) { if (existingResource) {
let domain; let domain;
if ( if (
["http", "ssh", "rdp", "vnc"].includes(resourceData.mode || "") ["http", "ssh", "rdp", "vnc"].includes(resourceData.mode || "")
) { ) {
if (resourceData["full-domain"]?.startsWith("*.")) {
const isLicensed = await isLicensedOrSubscribed(
orgId,
tierMatrix.wildcardSubdomain
);
if (!isLicensed) {
throw new Error(
"Wildcard subdomains are not supported on your current plan. Please upgrade to access this feature."
);
}
}
domain = await getDomain( domain = await getDomain(
existingResource.resourceId, existingResource.resourceId,
resourceData["full-domain"]!, resourceData["full-domain"]!,
orgId, orgId,
trx trx
); );
await enforceDomainNamespacePaywall(
orgId,
domain.domainId,
trx
);
} }
// check if the only key in the resource is targets, if so, skip the update // check if the only key in the resource is targets, if so, skip the update
@@ -522,6 +579,13 @@ export async function updateProxyResources(
? (resourceData["proxy-protocol-version"] ?? ? (resourceData["proxy-protocol-version"] ??
1) 1)
: 1, : 1,
pamMode:
resourceData["auth-daemon"]?.pam ||
"passthrough",
authDaemonMode:
resourceData["auth-daemon"]?.mode || "native",
authDaemonPort:
resourceData["auth-daemon"]?.port || 22123,
resourcePolicyId: null, resourcePolicyId: null,
defaultResourcePolicyId: inlinePolicyId defaultResourcePolicyId: inlinePolicyId
}) })
@@ -664,7 +728,8 @@ export async function updateProxyResources(
? "/" ? "/"
: undefined), : undefined),
rewritePathType: targetData["rewrite-match"], rewritePathType: targetData["rewrite-match"],
priority: targetData.priority priority: targetData.priority,
mode: resourceData.mode
}) })
.where(eq(targets.targetId, existingTarget.targetId)) .where(eq(targets.targetId, existingTarget.targetId))
.returning(); .returning();
@@ -906,12 +971,30 @@ export async function updateProxyResources(
if ( if (
["http", "ssh", "rdp", "vnc"].includes(resourceData.mode || "") ["http", "ssh", "rdp", "vnc"].includes(resourceData.mode || "")
) { ) {
if (resourceData["full-domain"]?.startsWith("*.")) {
const isLicensed = await isLicensedOrSubscribed(
orgId,
tierMatrix.wildcardSubdomain
);
if (!isLicensed) {
throw new Error(
"Wildcard subdomains are not supported on your current plan. Please upgrade to access this feature."
);
}
}
domain = await getDomain( domain = await getDomain(
undefined, undefined,
resourceData["full-domain"]!, resourceData["full-domain"]!,
orgId, orgId,
trx trx
); );
await enforceDomainNamespacePaywall(
orgId,
domain.domainId,
trx
);
} }
const isLicensed = await isLicensedOrSubscribed( const isLicensed = await isLicensedOrSubscribed(
@@ -1384,17 +1467,6 @@ async function syncWhitelistUsers(
.where(eq(resourceWhitelist.resourceId, resourceId)); .where(eq(resourceWhitelist.resourceId, resourceId));
for (const email of whitelistUsers) { for (const email of whitelistUsers) {
const [user] = await trx
.select()
.from(users)
.innerJoin(userOrgs, eq(users.userId, userOrgs.userId))
.where(and(eq(users.email, email), eq(userOrgs.orgId, orgId)))
.limit(1);
if (!user) {
throw new Error(`User not found: ${email} in org ${orgId}`);
}
const existingWhitelistEntry = existingWhitelist.find( const existingWhitelistEntry = existingWhitelist.find(
(w) => w.email === email (w) => w.email === email
); );
@@ -1866,6 +1938,37 @@ function checkIfTargetChanged(
return false; return false;
} }
async function enforceDomainNamespacePaywall(
orgId: string,
domainId: string,
trx: Transaction
) {
if (build !== "saas") {
return;
}
const hasDomainNamespaceAccess = await isLicensedOrSubscribed(
orgId,
tierMatrix.domainNamespaces
);
if (hasDomainNamespaceAccess) {
return;
}
const [namespaceDomain] = await trx
.select()
.from(domainNamespaces)
.where(eq(domainNamespaces.domainId, domainId))
.limit(1);
if (namespaceDomain) {
throw new Error(
"Your current subscription does not support custom domain namespaces. Please upgrade to access this feature."
);
}
}
export async function getDomain( export async function getDomain(
resourceId: number | undefined, resourceId: number | undefined,
fullDomain: string, fullDomain: string,
+653
View File
@@ -0,0 +1,653 @@
import {
db,
idp,
idpOrg,
resourcePolicies,
resourcePolicyHeaderAuth,
resourcePolicyPassword,
resourcePolicyPincode,
resourcePolicyRules,
resourcePolicyWhiteList,
rolePolicies,
roles,
Transaction,
userOrgs,
userPolicies,
users
} from "@server/db";
import { eq, and, or } from "drizzle-orm";
import { Config, ResourcePolicyData } from "./types";
import logger from "@server/logger";
import { getUniqueResourcePolicyName } from "@server/db/names";
import { hashPassword } from "@server/auth/password";
import { isValidCIDR, isValidIP, isValidUrlGlobPattern } from "../validators";
import { isLicensedOrSubscribed } from "#dynamic/lib/isLicencedOrSubscribed";
import { tierMatrix } from "../billing/tierMatrix";
export type ResourcePoliciesResults = {
resourcePolicyId: number;
niceId: string;
}[];
export async function updateResourcePolicies(
orgId: string,
config: Config,
trx: Transaction
): Promise<ResourcePoliciesResults> {
const results: ResourcePoliciesResults = [];
for (const [policyNiceId, policyData] of Object.entries(
config["public-policies"]
)) {
const isLicensed = await isLicensedOrSubscribed(
orgId,
tierMatrix.resourcePolicies
);
if (!isLicensed) {
throw new Error(
"Your current subscription does not support shared resource policies. Please upgrade to access this feature."
);
}
// Validate rules
for (const rule of policyData.rules) {
if (rule.match === "cidr" && !isValidCIDR(rule.value)) {
throw new Error(
`Invalid CIDR provided in resource policy '${policyNiceId}': ${rule.value}`
);
} else if (rule.match === "ip" && !isValidIP(rule.value)) {
throw new Error(
`Invalid IP provided in resource policy '${policyNiceId}': ${rule.value}`
);
} else if (
rule.match === "path" &&
!isValidUrlGlobPattern(rule.value)
) {
throw new Error(
`Invalid URL glob pattern provided in resource policy '${policyNiceId}': ${rule.value}`
);
}
}
// Validate auto-login-idp if provided
if (policyData["auto-login-idp"]) {
const [provider] = await trx
.select()
.from(idp)
.innerJoin(idpOrg, eq(idpOrg.idpId, idp.idpId))
.where(
and(
eq(idp.idpId, policyData["auto-login-idp"]),
eq(idpOrg.orgId, orgId)
)
)
.limit(1);
if (!provider) {
throw new Error(
`Identity provider not found for policy '${policyNiceId}' in this organization`
);
}
}
// Look up the admin role
const [adminRole] = await trx
.select()
.from(roles)
.where(and(eq(roles.isAdmin, true), eq(roles.orgId, orgId)))
.limit(1);
if (!adminRole) {
throw new Error("Admin role not found");
}
// Find existing policy by niceId and orgId
const [existingPolicy] = await trx
.select()
.from(resourcePolicies)
.where(
and(
eq(resourcePolicies.niceId, policyNiceId),
eq(resourcePolicies.orgId, orgId)
)
)
.limit(1);
let resourcePolicyId: number;
if (existingPolicy) {
// Update the existing policy
await trx
.update(resourcePolicies)
.set({
name: policyData.name,
sso: policyData.sso ?? true,
idpId: policyData["auto-login-idp"] ?? null,
emailWhitelistEnabled:
policyData["email-whitelist-enabled"] ??
policyData["whitelist-users"].length > 0,
applyRules:
policyData["apply-rules"] || policyData.rules.length > 0
})
.where(
eq(
resourcePolicies.resourcePolicyId,
existingPolicy.resourcePolicyId
)
);
resourcePolicyId = existingPolicy.resourcePolicyId;
// Sync password
await trx
.delete(resourcePolicyPassword)
.where(
eq(
resourcePolicyPassword.resourcePolicyId,
resourcePolicyId
)
);
if (policyData.password) {
const passwordHash = await hashPassword(policyData.password);
await trx.insert(resourcePolicyPassword).values({
resourcePolicyId,
passwordHash
});
}
// Sync pincode
await trx
.delete(resourcePolicyPincode)
.where(
eq(resourcePolicyPincode.resourcePolicyId, resourcePolicyId)
);
if (policyData.pincode) {
const pincodeHash = await hashPassword(policyData.pincode);
await trx.insert(resourcePolicyPincode).values({
resourcePolicyId,
pincodeHash,
digitLength: 6
});
}
// Sync header auth
await trx
.delete(resourcePolicyHeaderAuth)
.where(
eq(
resourcePolicyHeaderAuth.resourcePolicyId,
resourcePolicyId
)
);
if (policyData["basic-auth"]) {
const basicAuth = policyData["basic-auth"];
const headerAuthHash = await hashPassword(
Buffer.from(
`${basicAuth.user}:${basicAuth.password}`
).toString("base64")
);
await trx.insert(resourcePolicyHeaderAuth).values({
resourcePolicyId,
headerAuthHash,
extendedCompatibility:
basicAuth["extended-compatibility"] ?? true
});
}
// Sync SSO roles
await syncRolePolicies(
resourcePolicyId,
policyData["sso-roles"],
orgId,
adminRole.roleId,
trx
);
// Sync SSO users
await syncUserPolicies(
resourcePolicyId,
policyData["sso-users"],
orgId,
trx
);
// Sync whitelist users
await syncWhitelistPolicyUsers(
resourcePolicyId,
policyData["whitelist-users"],
trx
);
// Sync rules
await syncPolicyRules(resourcePolicyId, policyData.rules, trx);
logger.debug(
`Updated resource policy ${resourcePolicyId} (${policyNiceId})`
);
} else {
// Create a new policy
const [newPolicy] = await trx
.insert(resourcePolicies)
.values({
niceId: policyNiceId,
orgId,
name: policyData.name,
sso: policyData.sso ?? true,
idpId: policyData["auto-login-idp"] ?? null,
emailWhitelistEnabled:
policyData["email-whitelist-enabled"] ??
policyData["whitelist-users"].length > 0,
applyRules:
policyData["apply-rules"] ||
policyData.rules.length > 0,
scope: "global"
})
.returning();
resourcePolicyId = newPolicy.resourcePolicyId;
// Always add admin role
await trx.insert(rolePolicies).values({
roleId: adminRole.roleId,
resourcePolicyId
});
// Add SSO roles
await addRolePolicies(
resourcePolicyId,
policyData["sso-roles"],
orgId,
adminRole.roleId,
trx
);
// Add SSO users
await addUserPolicies(
resourcePolicyId,
policyData["sso-users"],
orgId,
trx
);
// Add password
if (policyData.password) {
const passwordHash = await hashPassword(policyData.password);
await trx.insert(resourcePolicyPassword).values({
resourcePolicyId,
passwordHash
});
}
// Add pincode
if (policyData.pincode) {
const pincodeHash = await hashPassword(policyData.pincode);
await trx.insert(resourcePolicyPincode).values({
resourcePolicyId,
pincodeHash,
digitLength: 6
});
}
// Add header auth
if (policyData["basic-auth"]) {
const basicAuth = policyData["basic-auth"];
const headerAuthHash = await hashPassword(
Buffer.from(
`${basicAuth.user}:${basicAuth.password}`
).toString("base64")
);
await trx.insert(resourcePolicyHeaderAuth).values({
resourcePolicyId,
headerAuthHash,
extendedCompatibility:
basicAuth["extended-compatibility"] ?? true
});
}
// Add whitelist users
if (policyData["whitelist-users"].length > 0) {
await trx.insert(resourcePolicyWhiteList).values(
policyData["whitelist-users"].map((email) => ({
email,
resourcePolicyId
}))
);
}
// Add rules
if (policyData.rules.length > 0) {
await trx.insert(resourcePolicyRules).values(
policyData.rules.map((rule, index) => ({
resourcePolicyId,
action: getRuleAction(rule.action),
match: getRuleMatch(rule.match),
value: rule.value,
priority: rule.priority ?? index + 1,
enabled: rule.enabled ?? true
}))
);
}
logger.debug(
`Created resource policy ${resourcePolicyId} (${policyNiceId})`
);
}
results.push({ resourcePolicyId, niceId: policyNiceId });
}
return results;
}
function getRuleAction(input: string): "ACCEPT" | "DROP" | "PASS" {
if (input === "allow") return "ACCEPT";
if (input === "deny") return "DROP";
return "PASS";
}
function getRuleMatch(
input: string
): "CIDR" | "IP" | "PATH" | "COUNTRY" | "ASN" | "REGION" {
return input.toUpperCase() as
| "CIDR"
| "IP"
| "PATH"
| "COUNTRY"
| "ASN"
| "REGION";
}
async function syncRolePolicies(
policyId: number,
ssoRoles: string[],
orgId: string,
adminRoleId: number,
trx: Transaction
) {
const existingRolePolicies = await trx
.select()
.from(rolePolicies)
.where(eq(rolePolicies.resourcePolicyId, policyId));
for (const roleName of ssoRoles) {
const [role] = await trx
.select()
.from(roles)
.where(and(eq(roles.name, roleName), eq(roles.orgId, orgId)))
.limit(1);
if (!role) {
logger.warn(
`Role '${roleName}' not found in org '${orgId}', skipping`
);
continue;
}
if (role.isAdmin) {
continue; // admin role is always included, skip
}
const alreadyExists = existingRolePolicies.some(
(rp) => rp.roleId === role.roleId
);
if (!alreadyExists) {
await trx.insert(rolePolicies).values({
roleId: role.roleId,
resourcePolicyId: policyId
});
}
}
// Remove roles no longer in the list (except admin)
for (const existingRolePolicy of existingRolePolicies) {
if (existingRolePolicy.roleId === adminRoleId) {
continue;
}
const [role] = await trx
.select()
.from(roles)
.where(eq(roles.roleId, existingRolePolicy.roleId))
.limit(1);
if (role?.isAdmin) {
continue;
}
if (role && !ssoRoles.includes(role.name)) {
await trx
.delete(rolePolicies)
.where(
and(
eq(rolePolicies.resourcePolicyId, policyId),
eq(rolePolicies.roleId, existingRolePolicy.roleId)
)
);
}
}
}
async function addRolePolicies(
policyId: number,
ssoRoles: string[],
orgId: string,
adminRoleId: number,
trx: Transaction
) {
for (const roleName of ssoRoles) {
const [role] = await trx
.select()
.from(roles)
.where(and(eq(roles.name, roleName), eq(roles.orgId, orgId)))
.limit(1);
if (!role) {
logger.warn(
`Role '${roleName}' not found in org '${orgId}', skipping`
);
continue;
}
if (role.isAdmin) {
continue; // admin already added
}
await trx.insert(rolePolicies).values({
roleId: role.roleId,
resourcePolicyId: policyId
});
}
}
async function syncUserPolicies(
policyId: number,
ssoUsers: string[],
orgId: string,
trx: Transaction
) {
const existingUserPolicies = await trx
.select()
.from(userPolicies)
.where(eq(userPolicies.resourcePolicyId, policyId));
for (const username of ssoUsers) {
const [user] = await trx
.select()
.from(users)
.innerJoin(userOrgs, eq(users.userId, userOrgs.userId))
.where(
and(
or(eq(users.username, username), eq(users.email, username)),
eq(userOrgs.orgId, orgId)
)
)
.limit(1);
if (!user) {
logger.warn(
`User '${username}' not found in org '${orgId}', skipping`
);
continue;
}
const alreadyExists = existingUserPolicies.some(
(up) => up.userId === user.user.userId
);
if (!alreadyExists) {
await trx.insert(userPolicies).values({
userId: user.user.userId,
resourcePolicyId: policyId
});
}
}
// Remove users no longer in the list
for (const existingUserPolicy of existingUserPolicies) {
const [user] = await trx
.select()
.from(users)
.innerJoin(userOrgs, eq(users.userId, userOrgs.userId))
.where(
and(
eq(users.userId, existingUserPolicy.userId),
eq(userOrgs.orgId, orgId)
)
)
.limit(1);
if (
user &&
user.user.username &&
!ssoUsers.includes(user.user.username) &&
!ssoUsers.includes(user.user.email ?? "")
) {
await trx
.delete(userPolicies)
.where(
and(
eq(userPolicies.resourcePolicyId, policyId),
eq(userPolicies.userId, existingUserPolicy.userId)
)
);
}
}
}
async function addUserPolicies(
policyId: number,
ssoUsers: string[],
orgId: string,
trx: Transaction
) {
for (const username of ssoUsers) {
const [user] = await trx
.select()
.from(users)
.innerJoin(userOrgs, eq(users.userId, userOrgs.userId))
.where(
and(
or(eq(users.username, username), eq(users.email, username)),
eq(userOrgs.orgId, orgId)
)
)
.limit(1);
if (!user) {
logger.warn(
`User '${username}' not found in org '${orgId}', skipping`
);
continue;
}
await trx.insert(userPolicies).values({
userId: user.user.userId,
resourcePolicyId: policyId
});
}
}
async function syncWhitelistPolicyUsers(
policyId: number,
whitelistUsers: string[],
trx: Transaction
) {
const existingWhitelist = await trx
.select()
.from(resourcePolicyWhiteList)
.where(eq(resourcePolicyWhiteList.resourcePolicyId, policyId));
for (const email of whitelistUsers) {
const alreadyExists = existingWhitelist.some((w) => w.email === email);
if (!alreadyExists) {
await trx.insert(resourcePolicyWhiteList).values({
email,
resourcePolicyId: policyId
});
}
}
for (const existingEntry of existingWhitelist) {
if (!whitelistUsers.includes(existingEntry.email)) {
await trx
.delete(resourcePolicyWhiteList)
.where(
and(
eq(resourcePolicyWhiteList.resourcePolicyId, policyId),
eq(resourcePolicyWhiteList.email, existingEntry.email)
)
);
}
}
}
async function syncPolicyRules(
policyId: number,
rules: ResourcePolicyData["rules"],
trx: Transaction
) {
const existingRules = await trx
.select()
.from(resourcePolicyRules)
.where(eq(resourcePolicyRules.resourcePolicyId, policyId))
.orderBy(resourcePolicyRules.priority);
for (const [index, rule] of rules.entries()) {
const intendedPriority = rule.priority ?? index + 1;
const existingRule = existingRules[index];
if (existingRule) {
await trx
.update(resourcePolicyRules)
.set({
action: getRuleAction(rule.action),
match: getRuleMatch(rule.match),
value: rule.value,
priority: intendedPriority,
enabled: rule.enabled ?? true
})
.where(eq(resourcePolicyRules.ruleId, existingRule.ruleId));
} else {
await trx.insert(resourcePolicyRules).values({
resourcePolicyId: policyId,
action: getRuleAction(rule.action),
match: getRuleMatch(rule.match),
value: rule.value,
priority: intendedPriority,
enabled: rule.enabled ?? true
});
}
}
// Remove extra rules
if (existingRules.length > rules.length) {
const rulesToDelete = existingRules.slice(rules.length);
for (const rule of rulesToDelete) {
await trx
.delete(resourcePolicyRules)
.where(eq(resourcePolicyRules.ruleId, rule.ruleId));
}
}
}
+150 -7
View File
@@ -1,8 +1,23 @@
import { z } from "zod"; import { z } from "zod";
import { existsSync } from "node:fs";
import { portRangeStringSchema } from "@server/lib/ip"; import { portRangeStringSchema } from "@server/lib/ip";
import { MaintenanceSchema } from "#dynamic/lib/blueprints/MaintenanceSchema"; import { MaintenanceSchema } from "#dynamic/lib/blueprints/MaintenanceSchema";
import { isValidRegionId } from "@server/db/regions"; import { isValidRegionId } from "@server/db/regions";
import { wildcardSubdomainSchema } from "@server/lib/schemas"; import { wildcardSubdomainSchema } from "@server/lib/schemas";
import config from "@server/lib/config";
const maxmindDbPath = config.getRawConfig().server.maxmind_db_path;
const maxmindAsnPath = config.getRawConfig().server.maxmind_asn_path;
const hasMaxmindCountryDb =
typeof maxmindDbPath === "string" &&
maxmindDbPath.length > 0 &&
existsSync(maxmindDbPath);
const hasMaxmindAsnDb =
typeof maxmindAsnPath === "string" &&
maxmindAsnPath.length > 0 &&
existsSync(maxmindAsnPath);
export const SiteSchema = z.object({ export const SiteSchema = z.object({
name: z.string().min(1).max(100), name: z.string().min(1).max(100),
@@ -83,7 +98,8 @@ export const RuleSchema = z
action: z.enum(["allow", "deny", "pass"]), action: z.enum(["allow", "deny", "pass"]),
match: z.enum(["cidr", "path", "ip", "country", "asn", "region"]), match: z.enum(["cidr", "path", "ip", "country", "asn", "region"]),
value: z.coerce.string(), value: z.coerce.string(),
priority: z.int().optional() priority: z.int().optional(),
enabled: z.boolean().optional().default(true)
}) })
.refine( .refine(
(rule) => { (rule) => {
@@ -116,6 +132,9 @@ export const RuleSchema = z
.refine( .refine(
(rule) => { (rule) => {
if (rule.match === "country") { if (rule.match === "country") {
if (!hasMaxmindCountryDb) {
return false;
}
// Check if it's a valid 2-letter country code or "ALL" // Check if it's a valid 2-letter country code or "ALL"
return /^[A-Z]{2}$/.test(rule.value) || rule.value === "ALL"; return /^[A-Z]{2}$/.test(rule.value) || rule.value === "ALL";
} }
@@ -124,12 +143,15 @@ export const RuleSchema = z
{ {
path: ["value"], path: ["value"],
message: message:
"Value must be a 2-letter country code or 'ALL' when match is 'country'" "Country rules require a valid existing server.maxmind_db_path and value must be a 2-letter country code or 'ALL'"
} }
) )
.refine( .refine(
(rule) => { (rule) => {
if (rule.match === "asn") { if (rule.match === "asn") {
if (!hasMaxmindCountryDb || !hasMaxmindAsnDb) {
return false;
}
// Check if it's either AS<number> format or "ALL" // Check if it's either AS<number> format or "ALL"
const asNumberPattern = /^AS\d+$/i; const asNumberPattern = /^AS\d+$/i;
return asNumberPattern.test(rule.value) || rule.value === "ALL"; return asNumberPattern.test(rule.value) || rule.value === "ALL";
@@ -139,7 +161,7 @@ export const RuleSchema = z
{ {
path: ["value"], path: ["value"],
message: message:
"Value must be 'AS<number>' format or 'ALL' when match is 'asn'" "ASN rules require valid existing server.maxmind_db_path and server.maxmind_asn_path, and value must be 'AS<number>' format or 'ALL'"
} }
) )
.refine( .refine(
@@ -267,8 +289,37 @@ export const PublicResourceSchema = z
return true; return true;
} }
// If protocol/mode is http, it must have a full-domain const effectiveProtocol = resource.mode ?? resource.protocol;
if ((resource.mode ?? resource.protocol) === "http") { if (effectiveProtocol !== "ssh") {
return true;
}
const authDaemonMode = resource["auth-daemon"]?.mode;
if (authDaemonMode !== "native" && authDaemonMode !== "site") {
return true;
}
return (
resource.targets.filter((target) => target != null).length <= 1
);
},
{
path: ["targets"],
error: "When protocol is 'ssh' and auth-daemon mode is 'native' or 'site', only one target/site is allowed"
}
)
.refine(
(resource) => {
if (isTargetsOnlyResource(resource)) {
return true;
}
// If protocol/mode is http, ssh, rdp, or vnc, it must have a full-domain
const effectiveProtocol = resource.mode ?? resource.protocol;
if (
effectiveProtocol !== undefined &&
["http", "ssh", "rdp", "vnc"].includes(effectiveProtocol)
) {
return ( return (
resource["full-domain"] !== undefined && resource["full-domain"] !== undefined &&
resource["full-domain"].length > 0 resource["full-domain"].length > 0
@@ -278,7 +329,7 @@ export const PublicResourceSchema = z
}, },
{ {
path: ["full-domain"], path: ["full-domain"],
error: "When protocol is 'http', a 'full-domain' must be provided" error: "When protocol is 'http', 'ssh', 'rdp', or 'vnc', a 'full-domain' must be provided"
} }
) )
.refine( .refine(
@@ -505,7 +556,90 @@ export const PrivateResourceSchema = z
{ {
message: "Destination must be a valid CIDR notation for cidr mode" message: "Destination must be a valid CIDR notation for cidr mode"
} }
); )
.refine(
(data) => {
if (data.mode !== "ssh") {
return true;
}
const authDaemonMode = data["auth-daemon"]?.mode;
if (authDaemonMode !== "native" && authDaemonMode !== "site") {
return true;
}
const uniqueSites = new Set<string>();
if (data.site) {
uniqueSites.add(data.site);
}
for (const site of data.sites) {
uniqueSites.add(site);
}
return uniqueSites.size <= 1;
},
{
path: ["sites"],
message:
"When mode is 'ssh' and auth-daemon mode is 'native' or 'site', only one site/target is allowed"
}
)
.transform((data) => {
if (
data.mode === "ssh" &&
data.destination !== undefined &&
data["destination-port"] === undefined
) {
data["destination-port"] = 22;
}
return data;
});
export const ResourcePolicyRuleSchema = RuleSchema;
export const ResourcePolicySchema = z.object({
name: z.string().min(1).max(255),
sso: z.boolean().optional().default(true),
"auto-login-idp": z.int().positive().optional().nullable(),
"sso-roles": z
.array(z.string())
.optional()
.default([])
.refine((roles) => !roles.includes("Admin"), {
error: "Admin role cannot be included in sso-roles"
}),
"sso-users": z.array(z.string()).optional().default([]),
password: z.string().min(4).max(100).optional().nullable(),
pincode: z
.string()
.regex(/^\d{6}$/)
.optional()
.nullable(),
"basic-auth": z
.object({
user: z.string().min(4).max(100),
password: z.string().min(4).max(100),
"extended-compatibility": z.boolean().default(true)
})
.optional()
.nullable(),
"email-whitelist-enabled": z.boolean().optional().default(false),
"whitelist-users": z
.array(
z.email().or(
z.string().regex(/^\*@[\w.-]+\.[a-zA-Z]{2,}$/, {
error: "Invalid email address. Wildcard (*) must be the entire local part."
})
)
)
.max(50)
.transform((v) => v.map((e) => e.toLowerCase()))
.optional()
.default([]),
"apply-rules": z.boolean().optional().default(false),
rules: z.array(ResourcePolicyRuleSchema).optional().default([])
});
export type ResourcePolicyData = z.infer<typeof ResourcePolicySchema>;
// Schema for the entire configuration object // Schema for the entire configuration object
export const ConfigSchema = z export const ConfigSchema = z
@@ -526,6 +660,10 @@ export const ConfigSchema = z
.record(z.string(), PrivateResourceSchema) .record(z.string(), PrivateResourceSchema)
.optional() .optional()
.prefault({}), .prefault({}),
"public-policies": z
.record(z.string(), ResourcePolicySchema)
.optional()
.prefault({}),
sites: z.record(z.string(), SiteSchema).optional().prefault({}) sites: z.record(z.string(), SiteSchema).optional().prefault({})
}) })
.transform((data) => { .transform((data) => {
@@ -556,6 +694,10 @@ export const ConfigSchema = z
string, string,
z.infer<typeof PrivateResourceSchema> z.infer<typeof PrivateResourceSchema>
>; >;
"public-policies": Record<
string,
z.infer<typeof ResourcePolicySchema>
>;
sites: Record<string, z.infer<typeof SiteSchema>>; sites: Record<string, z.infer<typeof SiteSchema>>;
}; };
}) })
@@ -695,3 +837,4 @@ export type Site = z.infer<typeof SiteSchema>;
export type Target = z.infer<typeof TargetSchema>; export type Target = z.infer<typeof TargetSchema>;
export type Resource = z.infer<typeof PublicResourceSchema>; export type Resource = z.infer<typeof PublicResourceSchema>;
export type Config = z.infer<typeof ConfigSchema>; export type Config = z.infer<typeof ConfigSchema>;
export type BlueprintResourcePolicy = z.infer<typeof ResourcePolicySchema>;
+15 -6
View File
@@ -504,7 +504,7 @@ export function generateRemoteSubnets(
const parseResult = cidrSchema.safeParse(sr.destination); const parseResult = cidrSchema.safeParse(sr.destination);
return parseResult.success; return parseResult.success;
} }
if (sr.mode === "host") { if (sr.mode === "host" || sr.mode === "ssh") {
// check if its a valid IP using zod // check if its a valid IP using zod
const ipSchema = z.union([z.ipv4(), z.ipv6()]); const ipSchema = z.union([z.ipv4(), z.ipv6()]);
const parseResult = ipSchema.safeParse(sr.destination); const parseResult = ipSchema.safeParse(sr.destination);
@@ -514,7 +514,7 @@ export function generateRemoteSubnets(
}) })
.map((sr) => { .map((sr) => {
if (sr.mode === "cidr") return sr.destination; if (sr.mode === "cidr") return sr.destination;
if (sr.mode === "host") { if (sr.mode === "host" || sr.mode === "ssh") {
return `${sr.destination}/32`; return `${sr.destination}/32`;
} }
return ""; // This should never be reached due to filtering, but satisfies TypeScript return ""; // This should never be reached due to filtering, but satisfies TypeScript
@@ -531,7 +531,7 @@ export function generateAliasConfig(allSiteResources: SiteResource[]): Alias[] {
.filter( .filter(
(sr) => (sr) =>
sr.aliasAddress && sr.aliasAddress &&
((sr.alias && sr.mode == "host") || ((sr.alias && (sr.mode == "host" || sr.mode == "ssh")) ||
(sr.fullDomain && sr.mode == "http")) (sr.fullDomain && sr.mode == "http"))
) )
.map((sr) => ({ .map((sr) => ({
@@ -577,6 +577,10 @@ export function generateSubnetProxyTargets(
continue; continue;
} }
if (!siteResource.destination) {
continue;
}
const clientPrefix = `${clientSite.subnet.split("/")[0]}/32`; const clientPrefix = `${clientSite.subnet.split("/")[0]}/32`;
const portRange = [ const portRange = [
...parsePortRangeString(siteResource.tcpPortRangeString, "tcp"), ...parsePortRangeString(siteResource.tcpPortRangeString, "tcp"),
@@ -584,7 +588,7 @@ export function generateSubnetProxyTargets(
]; ];
const disableIcmp = siteResource.disableIcmp ?? false; const disableIcmp = siteResource.disableIcmp ?? false;
if (siteResource.mode == "host") { if (siteResource.mode == "host" || siteResource.mode == "ssh") {
let destination = siteResource.destination; let destination = siteResource.destination;
// check if this is a valid ip // check if this is a valid ip
const ipSchema = z.union([z.ipv4(), z.ipv6()]); const ipSchema = z.union([z.ipv4(), z.ipv6()]);
@@ -665,7 +669,12 @@ export async function generateSubnetProxyTargetV2(
return; return;
} }
let targets: SubnetProxyTargetV2[] = []; if (!siteResource.destination) {
// ssh can have no destination
return;
}
const targets: SubnetProxyTargetV2[] = [];
const portRange = [ const portRange = [
...parsePortRangeString(siteResource.tcpPortRangeString, "tcp"), ...parsePortRangeString(siteResource.tcpPortRangeString, "tcp"),
@@ -673,7 +682,7 @@ export async function generateSubnetProxyTargetV2(
]; ];
const disableIcmp = siteResource.disableIcmp ?? false; const disableIcmp = siteResource.disableIcmp ?? false;
if (siteResource.mode == "host") { if (siteResource.mode == "host" || siteResource.mode == "ssh") {
let destination = siteResource.destination; let destination = siteResource.destination;
// check if this is a valid ip // check if this is a valid ip
const ipSchema = z.union([z.ipv4(), z.ipv6()]); const ipSchema = z.union([z.ipv4(), z.ipv6()]);
+4
View File
@@ -181,6 +181,7 @@ class TelemetryClient {
let numPrivResourceHosts = 0; let numPrivResourceHosts = 0;
let numPrivResourceCidr = 0; let numPrivResourceCidr = 0;
let numPrivResourceHttp = 0; let numPrivResourceHttp = 0;
let numPrivResourceSsh = 0;
for (const res of allPrivateResources) { for (const res of allPrivateResources) {
if (res.mode === "host") { if (res.mode === "host") {
numPrivResourceHosts += 1; numPrivResourceHosts += 1;
@@ -188,6 +189,8 @@ class TelemetryClient {
numPrivResourceCidr += 1; numPrivResourceCidr += 1;
} else if (res.mode === "http") { } else if (res.mode === "http") {
numPrivResourceHttp += 1; numPrivResourceHttp += 1;
} else if (res.mode === "ssh") {
numPrivResourceSsh += 1;
} }
if (res.alias) { if (res.alias) {
@@ -207,6 +210,7 @@ class TelemetryClient {
numPrivateResourceHosts: numPrivResourceHosts, numPrivateResourceHosts: numPrivResourceHosts,
numPrivateResourceCidr: numPrivResourceCidr, numPrivateResourceCidr: numPrivResourceCidr,
numPrivateResourceHttp: numPrivResourceHttp, numPrivateResourceHttp: numPrivResourceHttp,
numPrivateResourceSsh: numPrivResourceSsh,
numAlertRules: numAlertRules.count, numAlertRules: numAlertRules.count,
numUserDevices: userDevicesCount.count, numUserDevices: userDevicesCount.count,
numMachineClients: machineClients.count, numMachineClients: machineClients.count,
+4 -3
View File
@@ -44,7 +44,8 @@ export async function getTraefikConfig(
filterOutNamespaceDomains = false, // UNUSED BUT USED IN PRIVATE filterOutNamespaceDomains = false, // UNUSED BUT USED IN PRIVATE
generateLoginPageRouters = false, // UNUSED BUT USED IN PRIVATE generateLoginPageRouters = false, // UNUSED BUT USED IN PRIVATE
allowRawResources = true, allowRawResources = true,
allowMaintenancePage = true // UNUSED BUT USED IN PRIVATE allowMaintenancePage = true, // UNUSED BUT USED IN PRIVATE
allowBrowserGatewayResources = true
): Promise<any> { ): Promise<any> {
// Get resources with their targets and sites in a single optimized query // Get resources with their targets and sites in a single optimized query
// Start from sites on this exit node, then join to targets and resources // Start from sites on this exit node, then join to targets and resources
@@ -240,7 +241,7 @@ export async function getTraefikConfig(
continue; continue;
} }
if (resource.http) { if (resource.mode === "http") {
if (!resource.domainId || !resource.fullDomain) { if (!resource.domainId || !resource.fullDomain) {
continue; continue;
} }
@@ -572,7 +573,7 @@ export async function getTraefikConfig(
serviceName serviceName
].loadBalancer.serversTransport = transportName; ].loadBalancer.serversTransport = transportName;
} }
} else { } else if (resource.mode === "tcp" || resource.mode === "udp") {
// Non-HTTP (TCP/UDP) configuration // Non-HTTP (TCP/UDP) configuration
if (!resource.enableProxy || !resource.proxyPort) { if (!resource.enableProxy || !resource.proxyPort) {
continue; continue;
+41
View File
@@ -1,5 +1,7 @@
import z from "zod"; import z from "zod";
import ipaddr from "ipaddr.js"; import ipaddr from "ipaddr.js";
import { COUNTRIES } from "@server/db/countries";
import { isValidRegionId } from "@server/db/regions";
export function isValidCIDR(cidr: string): boolean { export function isValidCIDR(cidr: string): boolean {
return ( return (
@@ -67,6 +69,45 @@ export function isValidUrlGlobPattern(pattern: string): boolean {
return true; return true;
} }
export const RESOURCE_RULE_MATCH_TYPES = [
"CIDR",
"IP",
"PATH",
"COUNTRY",
"ASN",
"REGION"
] as const;
export type ResourceRuleMatchType = (typeof RESOURCE_RULE_MATCH_TYPES)[number];
export function getResourceRuleValueValidationError(
match: ResourceRuleMatchType,
value: string
): string | null {
switch (match) {
case "CIDR":
return isValidCIDR(value) ? null : "Invalid CIDR provided";
case "IP":
return isValidIP(value) ? null : "Invalid IP provided";
case "PATH":
return isValidUrlGlobPattern(value)
? null
: "Invalid URL glob pattern provided";
case "REGION":
return isValidRegionId(value) ? null : "Invalid region ID provided";
case "COUNTRY":
return COUNTRIES.some((country) => country.code === value)
? null
: "Invalid country code provided";
case "ASN":
return /^AS\d+$/i.test(value.trim())
? null
: "Invalid ASN provided";
default:
return "Invalid rule match type provided";
}
}
export function isUrlValid(url: string | undefined) { export function isUrlValid(url: string | undefined) {
if (!url) return true; // the link is optional in the schema so if it's empty it's valid if (!url) return true; // the link is optional in the schema so if it's empty it's valid
var pattern = new RegExp( var pattern = new RegExp(
+17 -28
View File
@@ -1,11 +1,15 @@
import { Request, Response, NextFunction } from "express"; import { Request, Response, NextFunction } from "express";
import { db, Resource } from "@server/db"; import { db, Resource } from "@server/db";
import { resources, userOrgs, userResources, roleResources } from "@server/db"; import { resources, userOrgs } from "@server/db";
import { and, eq, inArray } from "drizzle-orm"; import { and, eq } from "drizzle-orm";
import createHttpError from "http-errors"; import createHttpError from "http-errors";
import HttpCode from "@server/types/HttpCode"; import HttpCode from "@server/types/HttpCode";
import { checkOrgAccessPolicy } from "#dynamic/lib/checkOrgAccessPolicy"; import { checkOrgAccessPolicy } from "#dynamic/lib/checkOrgAccessPolicy";
import { getUserOrgRoleIds } from "@server/lib/userOrgRoles"; import { getUserOrgRoleIds } from "@server/lib/userOrgRoles";
import {
getRoleResourceAccess,
getUserResourceAccess
} from "@server/db/queries/verifySessionQueries";
export async function verifyResourceAccess( export async function verifyResourceAccess(
req: Request, req: Request,
@@ -116,37 +120,22 @@ export async function verifyResourceAccess(
const roleResourceAccess = const roleResourceAccess =
(req.userOrgRoleIds?.length ?? 0) > 0 (req.userOrgRoleIds?.length ?? 0) > 0
? await db ? await getRoleResourceAccess(
.select() resource.resourceId,
.from(roleResources) req.userOrgRoleIds!
.where( )
and( : null;
eq(roleResources.resourceId, resource.resourceId),
inArray(
roleResources.roleId,
req.userOrgRoleIds!
)
)
)
.limit(1)
: [];
if (roleResourceAccess.length > 0) { if (roleResourceAccess) {
return next(); return next();
} }
const userResourceAccess = await db const userResourceAccess = await getUserResourceAccess(
.select() userId,
.from(userResources) resource.resourceId
.where( );
and(
eq(userResources.userId, userId),
eq(userResources.resourceId, resource.resourceId)
)
)
.limit(1);
if (userResourceAccess.length > 0) { if (userResourceAccess) {
return next(); return next();
} }
+322
View File
@@ -0,0 +1,322 @@
import { assertEquals } from "@test/assert";
/**
* Tests for the cross-organization site binding prevention in verifySiteAccess.
*
* verifySiteAccess now includes a check: if req.userOrgId is already set by a
* previous middleware (e.g. verifyResourceAccess or verifyTargetAccess), and the
* loaded site's orgId differs from req.userOrgId, the request is rejected with
* 403 Forbidden.
*
* Route stacks after fix:
* PUT /resource/:resourceId/target
* verifyResourceAccess verifySiteAccess verifyLimits ...
* POST /target/:targetId
* verifyTargetAccess verifySiteAccess verifyLimits ...
*
* verifyResourceAccess sets req.userOrgId to the resource's org.
* verifyTargetAccess sets req.userOrgId to the target's resource org.
* verifySiteAccess then checks site.orgId against req.userOrgId before
* overwriting it with the site's org.
*/
// --- Core org-matching logic (mirrors the check in verifySiteAccess) ---
function siteOrgMatchesExpectedOrg(
siteOrgId: string | null | undefined,
expectedOrgId: string | null | undefined
): boolean {
if (!siteOrgId || !expectedOrgId) {
return false;
}
return siteOrgId === expectedOrgId;
}
// Simulates the condition check in verifySiteAccess:
// if (req.userOrgId && site.orgId !== req.userOrgId) { reject }
function shouldRejectCrossOrgSite(
siteOrgId: string,
reqUserOrgId: string | undefined
): boolean {
// The actual check in verifySiteAccess is:
// if (req.userOrgId && site.orgId !== req.userOrgId) { reject }
return !!(reqUserOrgId && siteOrgId !== reqUserOrgId);
}
// --- Tests ---
function testSiteOrgMatchLogic() {
console.log("Running verifySiteAccess org-match logic tests...");
// Test 1: Same org — should match
{
const result = siteOrgMatchesExpectedOrg(
"org-attacker",
"org-attacker"
);
assertEquals(result, true, "Same org should match");
}
// Test 2: Different org — should NOT match (cross-org bypass scenario)
{
const result = siteOrgMatchesExpectedOrg("org-victim", "org-attacker");
assertEquals(
result,
false,
"Cross-org site should NOT match expected org"
);
}
// Test 3: Site orgId is null — should NOT match
{
const result = siteOrgMatchesExpectedOrg(null, "org-attacker");
assertEquals(result, false, "Null site orgId should NOT match");
}
// Test 4: Expected orgId is null — should NOT match
{
const result = siteOrgMatchesExpectedOrg("org-attacker", null);
assertEquals(result, false, "Null expected orgId should NOT match");
}
// Test 5: Both null — should NOT match
{
const result = siteOrgMatchesExpectedOrg(null, null);
assertEquals(result, false, "Both null should NOT match");
}
// Test 6: Empty string orgIds — should NOT match (empty string is falsy)
{
const result = siteOrgMatchesExpectedOrg("", "org-attacker");
assertEquals(result, false, "Empty site orgId should NOT match");
}
// Test 7: Undefined orgIds — should NOT match
{
const result = siteOrgMatchesExpectedOrg(undefined, "org-attacker");
assertEquals(result, false, "Undefined site orgId should NOT match");
}
console.log("All verifySiteAccess org-match logic tests passed.");
}
function testShouldRejectCrossOrgSite() {
console.log(
"Running shouldRejectCrossOrgSite tests (mirrors verifySiteAccess check)..."
);
// Test: No prior org context (undefined) — should NOT reject
// This is the normal case for site-only routes (e.g. PUT /site/:siteId)
// where verifySiteAccess runs without a prior verifyResourceAccess.
{
const shouldReject = shouldRejectCrossOrgSite("org-victim", undefined);
assertEquals(
shouldReject,
false,
"No prior org context should NOT reject (normal site routes)"
);
}
// Test: Same org — should NOT reject
{
const shouldReject = shouldRejectCrossOrgSite(
"org-attacker",
"org-attacker"
);
assertEquals(shouldReject, false, "Same org should NOT reject");
}
// Test: Different org — should reject
{
const shouldReject = shouldRejectCrossOrgSite(
"org-victim",
"org-attacker"
);
assertEquals(shouldReject, true, "Cross-org site should be rejected");
}
// Test: Empty string userOrgId — should NOT reject (falsy, check is skipped)
{
const shouldReject = shouldRejectCrossOrgSite("org-victim", "");
assertEquals(
shouldReject,
false,
"Empty string userOrgId should NOT reject (check is skipped)"
);
}
console.log("All shouldRejectCrossOrgSite tests passed.");
}
// --- Route stack validation tests ---
function testRouteStackOrdering() {
console.log("Running route stack ordering tests...");
const createTargetStack = [
"verifyResourceAccess",
"verifySiteAccess",
"verifyLimits",
"verifyUserHasAction",
"logActionAudit",
"createTarget"
];
const updateTargetStack = [
"verifyTargetAccess",
"verifySiteAccess",
"verifyLimits",
"verifyUserHasAction",
"logActionAudit",
"updateTarget"
];
// Verify verifySiteAccess comes after resource/target access middleware
{
const siteAccessIndex = createTargetStack.indexOf("verifySiteAccess");
const resourceAccessIndex = createTargetStack.indexOf(
"verifyResourceAccess"
);
assertEquals(
siteAccessIndex > resourceAccessIndex,
true,
"verifySiteAccess must come after verifyResourceAccess in create target stack"
);
}
{
const siteAccessIndex = updateTargetStack.indexOf("verifySiteAccess");
const targetAccessIndex =
updateTargetStack.indexOf("verifyTargetAccess");
assertEquals(
siteAccessIndex > targetAccessIndex,
true,
"verifySiteAccess must come after verifyTargetAccess in update target stack"
);
}
// Verify verifySiteAccess comes before the handler
{
const siteAccessIndex = createTargetStack.indexOf("verifySiteAccess");
const handlerIndex = createTargetStack.indexOf("createTarget");
assertEquals(
siteAccessIndex < handlerIndex,
true,
"verifySiteAccess must come before createTarget handler"
);
}
{
const siteAccessIndex = updateTargetStack.indexOf("verifySiteAccess");
const handlerIndex = updateTargetStack.indexOf("updateTarget");
assertEquals(
siteAccessIndex < handlerIndex,
true,
"verifySiteAccess must come before updateTarget handler"
);
}
console.log("All route stack ordering tests passed.");
}
// --- Security scenario tests ---
function testSecurityScenarios() {
console.log("Running security scenario tests...");
// Scenario 1: Attacker has resource access in org_attacker, but tries to
// bind target to a site in org_victim.
// verifyResourceAccess passes (sets req.userOrgId = "org_attacker").
// verifySiteAccess loads site (org_victim), checks site.orgId !== req.userOrgId.
// Expected: 403 Forbidden.
{
const shouldReject = shouldRejectCrossOrgSite(
"org_victim",
"org_attacker"
);
assertEquals(
shouldReject,
true,
"Scenario 1: Cross-org site binding must be rejected"
);
}
// Scenario 2: Attacker has resource access AND site access in another org.
// Even though the user has site access, verifySiteAccess rejects because
// the org-match check runs before the site access check.
// Expected: 403 Forbidden (org mismatch caught before site access check).
{
const shouldReject = shouldRejectCrossOrgSite(
"org_victim",
"org_attacker"
);
assertEquals(
shouldReject,
true,
"Scenario 2: Cross-org site must be rejected even if user has site access"
);
}
// Scenario 3: Legitimate user creates target with site in same org.
// verifyResourceAccess passes, verifySiteAccess org-match passes (same org),
// verifySiteAccess site access passes.
// Expected: 201 Created.
{
const shouldReject = shouldRejectCrossOrgSite(
"org_attacker",
"org_attacker"
);
assertEquals(
shouldReject,
false,
"Scenario 3: Same-org site must be allowed"
);
}
// Scenario 4: WireGuard site in victim org — org mismatch is caught before
// any DB write, pickPort, addPeer, or addTargets side effect.
{
const shouldReject = shouldRejectCrossOrgSite(
"org_victim",
"org_attacker"
);
assertEquals(
shouldReject,
true,
"Scenario 4: WireGuard cross-org site must be rejected before addPeer"
);
}
// Scenario 5: Newt site in victim org — same as scenario 4 but for newt.
{
const shouldReject = shouldRejectCrossOrgSite(
"org_victim",
"org_attacker"
);
assertEquals(
shouldReject,
true,
"Scenario 5: Newt cross-org site must be rejected before addTargets"
);
}
// Scenario 6: Normal site-only route (e.g. PUT /site/:siteId) where
// verifySiteAccess runs without a prior verifyResourceAccess.
// req.userOrgId is undefined, so the org-match check is skipped.
// Normal site access verification proceeds.
{
const shouldReject = shouldRejectCrossOrgSite("org_victim", undefined);
assertEquals(
shouldReject,
false,
"Scenario 6: Site-only routes should skip org-match check"
);
}
console.log("All security scenario tests passed.");
}
// Run all tests
testSiteOrgMatchLogic();
testShouldRejectCrossOrgSite();
testRouteStackOrdering();
testSecurityScenarios();
+10 -4
View File
@@ -71,6 +71,15 @@ export async function verifySiteAccess(
); );
} }
if (req.userOrgId && site.orgId !== req.userOrgId) {
return next(
createHttpError(
HttpCode.FORBIDDEN,
"User does not have access to this site"
)
);
}
if (!req.userOrg) { if (!req.userOrg) {
// Get user's role ID in the organization // Get user's role ID in the organization
const userOrgRole = await db const userOrgRole = await db
@@ -128,10 +137,7 @@ export async function verifySiteAccess(
.where( .where(
and( and(
eq(roleSites.siteId, site.siteId), eq(roleSites.siteId, site.siteId),
inArray( inArray(roleSites.roleId, req.userOrgRoleIds!)
roleSites.roleId,
req.userOrgRoleIds!
)
) )
) )
.limit(1) .limit(1)
+5 -1
View File
@@ -109,7 +109,11 @@ export const privateConfigSchema = z
enable_redis: z.boolean().optional().default(false), enable_redis: z.boolean().optional().default(false),
use_pangolin_dns: z.boolean().optional().default(false), use_pangolin_dns: z.boolean().optional().default(false),
use_org_only_idp: z.boolean().optional(), use_org_only_idp: z.boolean().optional(),
enable_acme_cert_sync: z.boolean().optional().default(true) enable_acme_cert_sync: z.boolean().optional().default(true),
disable_private_http_placeholder: z
.boolean()
.optional()
.default(false)
}) })
.optional() .optional()
.prefault({}), .prefault({}),
+44 -76
View File
@@ -12,7 +12,6 @@
*/ */
import { import {
browserGatewayTarget,
certificates, certificates,
db, db,
domainNamespaces, domainNamespaces,
@@ -172,8 +171,15 @@ export async function getTraefikConfig(
), ),
inArray(sites.type, siteTypes), inArray(sites.type, siteTypes),
allowRawResources allowRawResources
? inArray(resources.mode, ["http", "udp", "tcp"]) // allow all three ? inArray(resources.mode, [
: eq(resources.mode, "http") "http",
"udp",
"tcp",
"vnc",
"ssh",
"rdp"
]) // allow all three
: inArray(resources.mode, ["http", "vnc", "ssh", "rdp"])
) )
) )
.orderBy(desc(targets.priority), targets.targetId); // stable ordering .orderBy(desc(targets.priority), targets.targetId); // stable ordering
@@ -181,7 +187,10 @@ export async function getTraefikConfig(
// Group by resource and include targets with their unique site data // Group by resource and include targets with their unique site data
const resourcesMap = new Map(); const resourcesMap = new Map();
resourcesWithTargetsAndSites.forEach((row) => { for (const row of resourcesWithTargetsAndSites) {
if (!["http", "tcp", "udp"].includes(row.mode)) {
continue;
}
const resourceId = row.resourceId; const resourceId = row.resourceId;
const resourceName = sanitize(row.resourceName) || ""; const resourceName = sanitize(row.resourceName) || "";
const targetPath = encodePath(row.path); // Use encodePath to avoid collisions (e.g. "/a/b" vs "/a-b") const targetPath = encodePath(row.path); // Use encodePath to avoid collisions (e.g. "/a/b" vs "/a-b")
@@ -191,7 +200,7 @@ export async function getTraefikConfig(
const priority = row.priority ?? 100; const priority = row.priority ?? 100;
if (filterOutNamespaceDomains && row.domainNamespaceId) { if (filterOutNamespaceDomains && row.domainNamespaceId) {
return; continue;
} }
// Create a unique key combining resourceId, path config, and rewrite config // Create a unique key combining resourceId, path config, and rewrite config
@@ -218,7 +227,7 @@ export async function getTraefikConfig(
logger.debug( logger.debug(
`Invalid path rewrite configuration for resource ${resourceId}: ${validation.error}` `Invalid path rewrite configuration for resource ${resourceId}: ${validation.error}`
); );
return; continue;
} }
resourcesMap.set(mapKey, { resourcesMap.set(mapKey, {
@@ -275,7 +284,7 @@ export async function getTraefikConfig(
online: row.siteOnline online: row.siteOnline
} }
}); });
}); }
// Group browser gateway targets by resource // Group browser gateway targets by resource
type BrowserGatewayResourceEntry = { type BrowserGatewayResourceEntry = {
@@ -295,13 +304,12 @@ export async function getTraefikConfig(
maintenanceMessage: string | null; maintenanceMessage: string | null;
maintenanceEstimatedTime: string | null; maintenanceEstimatedTime: string | null;
targets: { targets: {
browserGatewayTargetId: number; targetId: number;
bgType: string; bgType: string;
siteId: number; siteId: number;
siteType: string; siteType: string;
siteOnline: boolean | null; siteOnline: boolean | null;
subnet: string | null; subnet: string | null;
siteExitNodeId: number | null;
}[]; }[];
}; };
const browserGatewayResourcesMap = new Map< const browserGatewayResourcesMap = new Map<
@@ -310,66 +318,10 @@ export async function getTraefikConfig(
>(); >();
if (allowBrowserGatewayResources) { if (allowBrowserGatewayResources) {
// Query browser gateway targets for this exit node for (const row of resourcesWithTargetsAndSites) {
const browserGatewayRows = await db if (!["ssh", "vnc", "rdp"].includes(row.mode)) {
.select({ continue;
// 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,
// Maintenance fields
maintenanceModeEnabled: resources.maintenanceModeEnabled,
maintenanceModeType: resources.maintenanceModeType,
maintenanceTitle: resources.maintenanceTitle,
maintenanceMessage: resources.maintenanceMessage,
maintenanceEstimatedTime: resources.maintenanceEstimatedTime,
// 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)
)
);
for (const row of browserGatewayRows) {
if (filterOutNamespaceDomains && row.domainNamespaceId) { if (filterOutNamespaceDomains && row.domainNamespaceId) {
continue; continue;
} }
@@ -394,13 +346,12 @@ export async function getTraefikConfig(
}); });
} }
browserGatewayResourcesMap.get(row.resourceId)!.targets.push({ browserGatewayResourcesMap.get(row.resourceId)!.targets.push({
browserGatewayTargetId: row.browserGatewayTargetId, targetId: row.targetId,
bgType: row.bgType, bgType: row.mode,
siteId: row.siteId, siteId: row.siteId,
siteType: row.siteType, siteType: row.siteType,
siteOnline: row.siteOnline, siteOnline: row.siteOnline,
subnet: row.subnet, subnet: row.subnet
siteExitNodeId: row.siteExitNodeId
}); });
} }
} }
@@ -410,7 +361,11 @@ export async function getTraefikConfig(
fullDomain: string | null; fullDomain: string | null;
mode: "http" | "host" | "cidr" | "ssh"; mode: "http" | "host" | "cidr" | "ssh";
}[] = []; }[] = [];
if (build == "enterprise") { if (
build == "enterprise" &&
!privateConfig.getRawPrivateConfig().flags
.disable_private_http_placeholder
) {
// we dont want to do this on the cloud // we dont want to do this on the cloud
// Query siteResources in HTTP mode with SSL enabled and aliases - cert generation / HTTPS edge // Query siteResources in HTTP mode with SSL enabled and aliases - cert generation / HTTPS edge
siteResourcesWithFullDomain = await db siteResourcesWithFullDomain = await db
@@ -493,16 +448,29 @@ export async function getTraefikConfig(
const transportName = `${key}-transport`; const transportName = `${key}-transport`;
const headersMiddlewareName = `${key}-headers-middleware`; const headersMiddlewareName = `${key}-headers-middleware`;
logger.debug(
`Processing resource ${resource.name} with domain ${fullDomain} and ${targets.length} targets`
);
if (!resource.enabled) { if (!resource.enabled) {
logger.debug(
`Resource ${resource.name} is disabled, skipping Traefik config`
);
continue; continue;
} }
if (resource.http) { if (resource.mode == "http") {
if (!resource.domainId) { if (!resource.domainId) {
logger.debug(
`Resource ${resource.name} does not have a domainId, skipping Traefik config`
);
continue; continue;
} }
if (!resource.fullDomain) { if (!resource.fullDomain) {
logger.debug(
`Resource ${resource.name} does not have a fullDomain, skipping Traefik config`
);
continue; continue;
} }
@@ -958,7 +926,7 @@ export async function getTraefikConfig(
serviceName serviceName
].loadBalancer.serversTransport = transportName; ].loadBalancer.serversTransport = transportName;
} }
} else { } else if (resource.mode == "tcp" || resource.mode == "udp") {
// Non-HTTP (TCP/UDP) configuration // Non-HTTP (TCP/UDP) configuration
if (!resource.enableProxy) { if (!resource.enableProxy) {
continue; continue;
@@ -208,7 +208,7 @@ registry.registerPath({
content: { content: {
"application/json": { "application/json": {
schema: z.object({ schema: z.object({
data: z.unknown().nullable(), data: z.record(z.string(), z.any()).nullable(),
success: z.boolean(), success: z.boolean(),
error: z.boolean(), error: z.boolean(),
message: z.string(), message: z.string(),
@@ -44,7 +44,7 @@ registry.registerPath({
content: { content: {
"application/json": { "application/json": {
schema: z.object({ schema: z.object({
data: z.unknown().nullable(), data: z.record(z.string(), z.any()).nullable(),
success: z.boolean(), success: z.boolean(),
error: z.boolean(), error: z.boolean(),
message: z.string(), message: z.string(),
@@ -112,4 +112,4 @@ export async function deleteAlertRule(
createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred") createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred")
); );
} }
} }
@@ -32,7 +32,10 @@ import { OpenAPITags, registry } from "@server/openApi";
import { and, eq } from "drizzle-orm"; import { and, eq } from "drizzle-orm";
import { decrypt } from "@server/lib/crypto"; import { decrypt } from "@server/lib/crypto";
import config from "@server/lib/config"; import config from "@server/lib/config";
import { GetAlertRuleResponse, WebhookAlertConfig } from "@server/routers/alertRule/types"; import {
GetAlertRuleResponse,
WebhookAlertConfig
} from "@server/routers/alertRule/types";
const paramsSchema = z const paramsSchema = z
.object({ .object({
@@ -55,7 +58,7 @@ registry.registerPath({
content: { content: {
"application/json": { "application/json": {
schema: z.object({ schema: z.object({
data: z.unknown().nullable(), data: z.record(z.string(), z.any()).nullable(),
success: z.boolean(), success: z.boolean(),
error: z.boolean(), error: z.boolean(),
message: z.string(), message: z.string(),
@@ -101,7 +101,7 @@ registry.registerPath({
content: { content: {
"application/json": { "application/json": {
schema: z.object({ schema: z.object({
data: z.unknown().nullable(), data: z.record(z.string(), z.any()).nullable(),
success: z.boolean(), success: z.boolean(),
error: z.boolean(), error: z.boolean(),
message: z.string(), message: z.string(),
@@ -44,7 +44,7 @@ registry.registerPath({
content: { content: {
"application/json": { "application/json": {
schema: z.object({ schema: z.object({
data: z.unknown().nullable(), data: z.record(z.string(), z.any()).nullable(),
success: z.boolean(), success: z.boolean(),
error: z.boolean(), error: z.boolean(),
message: z.string(), message: z.string(),
@@ -44,7 +44,7 @@ registry.registerPath({
content: { content: {
"application/json": { "application/json": {
schema: z.object({ schema: z.object({
data: z.unknown().nullable(), data: z.record(z.string(), z.any()).nullable(),
success: z.boolean(), success: z.boolean(),
error: z.boolean(), error: z.boolean(),
message: z.string(), message: z.string(),
@@ -44,7 +44,7 @@ registry.registerPath({
content: { content: {
"application/json": { "application/json": {
schema: z.object({ schema: z.object({
data: z.unknown().nullable(), data: z.record(z.string(), z.any()).nullable(),
success: z.boolean(), success: z.boolean(),
error: z.boolean(), error: z.boolean(),
message: z.string(), message: z.string(),
@@ -72,7 +72,9 @@ export async function exportConnectionAuditLogs(
); );
} }
const parsedParams = queryConnectionAuditLogsParams.safeParse(req.params); const parsedParams = queryConnectionAuditLogsParams.safeParse(
req.params
);
if (!parsedParams.success) { if (!parsedParams.success) {
return next( return next(
createHttpError( createHttpError(
@@ -112,4 +114,4 @@ export async function exportConnectionAuditLogs(
createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred") createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred")
); );
} }
} }
@@ -11,7 +11,14 @@
* This file is not licensed under the AGPLv3. * This file is not licensed under the AGPLv3.
*/ */
import { accessAuditLog, logsDb, resources, siteResources, db, primaryDb } from "@server/db"; import {
accessAuditLog,
logsDb,
resources,
siteResources,
db,
primaryDb
} from "@server/db";
import { registry } from "@server/openApi"; import { registry } from "@server/openApi";
import { NextFunction } from "express"; import { NextFunction } from "express";
import { Request, Response } from "express"; import { Request, Response } from "express";
@@ -150,21 +157,30 @@ export function queryAccess(data: Q) {
.orderBy(desc(accessAuditLog.timestamp), desc(accessAuditLog.id)); .orderBy(desc(accessAuditLog.timestamp), desc(accessAuditLog.id));
} }
async function enrichWithResourceDetails(logs: Awaited<ReturnType<typeof queryAccess>>) { async function enrichWithResourceDetails(
logs: Awaited<ReturnType<typeof queryAccess>>
) {
const resourceIds = logs const resourceIds = logs
.map(log => log.resourceId) .map((log) => log.resourceId)
.filter((id): id is number => id !== null && id !== undefined); .filter((id): id is number => id !== null && id !== undefined);
const siteResourceIds = logs const siteResourceIds = logs
.filter(log => log.resourceId == null && log.siteResourceId != null) .filter((log) => log.resourceId == null && log.siteResourceId != null)
.map(log => log.siteResourceId) .map((log) => log.siteResourceId)
.filter((id): id is number => id !== null && id !== undefined); .filter((id): id is number => id !== null && id !== undefined);
if (resourceIds.length === 0 && siteResourceIds.length === 0) { if (resourceIds.length === 0 && siteResourceIds.length === 0) {
return logs.map(log => ({ ...log, resourceName: null, resourceNiceId: null })); return logs.map((log) => ({
...log,
resourceName: null,
resourceNiceId: null
}));
} }
const resourceMap = new Map<number, { name: string | null; niceId: string | null }>(); const resourceMap = new Map<
number,
{ name: string | null; niceId: string | null }
>();
if (resourceIds.length > 0) { if (resourceIds.length > 0) {
const resourceDetails = await primaryDb const resourceDetails = await primaryDb
@@ -181,7 +197,10 @@ async function enrichWithResourceDetails(logs: Awaited<ReturnType<typeof queryAc
} }
} }
const siteResourceMap = new Map<number, { name: string | null; niceId: string | null }>(); const siteResourceMap = new Map<
number,
{ name: string | null; niceId: string | null }
>();
if (siteResourceIds.length > 0) { if (siteResourceIds.length > 0) {
const siteResourceDetails = await primaryDb const siteResourceDetails = await primaryDb
@@ -194,12 +213,15 @@ async function enrichWithResourceDetails(logs: Awaited<ReturnType<typeof queryAc
.where(inArray(siteResources.siteResourceId, siteResourceIds)); .where(inArray(siteResources.siteResourceId, siteResourceIds));
for (const r of siteResourceDetails) { for (const r of siteResourceDetails) {
siteResourceMap.set(r.siteResourceId, { name: r.name, niceId: r.niceId }); siteResourceMap.set(r.siteResourceId, {
name: r.name,
niceId: r.niceId
});
} }
} }
// Enrich logs with resource details // Enrich logs with resource details
return logs.map(log => { return logs.map((log) => {
if (log.resourceId != null) { if (log.resourceId != null) {
const details = resourceMap.get(log.resourceId); const details = resourceMap.get(log.resourceId);
return { return {
@@ -273,11 +295,11 @@ async function queryUniqueFilterAttributes(
// Fetch resource names from main database for the unique resource IDs // Fetch resource names from main database for the unique resource IDs
const resourceIds = uniqueResources const resourceIds = uniqueResources
.map(row => row.id) .map((row) => row.id)
.filter((id): id is number => id !== null); .filter((id): id is number => id !== null);
const siteResourceIds = uniqueSiteResources const siteResourceIds = uniqueSiteResources
.map(row => row.id) .map((row) => row.id)
.filter((id): id is number => id !== null); .filter((id): id is number => id !== null);
let resourcesWithNames: Array<{ id: number; name: string | null }> = []; let resourcesWithNames: Array<{ id: number; name: string | null }> = [];
@@ -293,7 +315,7 @@ async function queryUniqueFilterAttributes(
resourcesWithNames = [ resourcesWithNames = [
...resourcesWithNames, ...resourcesWithNames,
...resourceDetails.map(r => ({ ...resourceDetails.map((r) => ({
id: r.resourceId, id: r.resourceId,
name: r.name name: r.name
})) }))
@@ -311,7 +333,7 @@ async function queryUniqueFilterAttributes(
resourcesWithNames = [ resourcesWithNames = [
...resourcesWithNames, ...resourcesWithNames,
...siteResourceDetails.map(r => ({ ...siteResourceDetails.map((r) => ({
id: r.siteResourceId, id: r.siteResourceId,
name: r.name name: r.name
})) }))
@@ -344,7 +366,7 @@ registry.registerPath({
content: { content: {
"application/json": { "application/json": {
schema: z.object({ schema: z.object({
data: z.unknown().nullable(), data: z.record(z.string(), z.any()).nullable(),
success: z.boolean(), success: z.boolean(),
error: z.boolean(), error: z.boolean(),
message: z.string(), message: z.string(),
@@ -171,7 +171,7 @@ registry.registerPath({
content: { content: {
"application/json": { "application/json": {
schema: z.object({ schema: z.object({
data: z.unknown().nullable(), data: z.record(z.string(), z.any()).nullable(),
success: z.boolean(), success: z.boolean(),
error: z.boolean(), error: z.boolean(),
message: z.string(), message: z.string(),
@@ -459,7 +459,7 @@ registry.registerPath({
content: { content: {
"application/json": { "application/json": {
schema: z.object({ schema: z.object({
data: z.unknown().nullable(), data: z.record(z.string(), z.any()).nullable(),
success: z.boolean(), success: z.boolean(),
error: z.boolean(), error: z.boolean(),
message: z.string(), message: z.string(),
+14 -11
View File
@@ -17,8 +17,7 @@ import createHttpError from "http-errors";
import { z } from "zod"; import { z } from "zod";
import { fromError } from "zod-validation-error"; import { fromError } from "zod-validation-error";
import logger from "@server/logger"; import logger from "@server/logger";
import { sessions, sessionTransferToken } from "@server/db"; import { db, safeRead, sessions, sessionTransferToken } from "@server/db";
import { db } from "@server/db";
import { eq } from "drizzle-orm"; import { eq } from "drizzle-orm";
import { response } from "@server/lib/response"; import { response } from "@server/lib/response";
import { encodeHexLowerCase } from "@oslojs/encoding"; import { encodeHexLowerCase } from "@oslojs/encoding";
@@ -57,15 +56,19 @@ export async function transferSession(
sha256(new TextEncoder().encode(token)) sha256(new TextEncoder().encode(token))
); );
const [existing] = await db const result = await safeRead((db) =>
.select() db
.from(sessionTransferToken) .select()
.where(eq(sessionTransferToken.token, tokenRaw)) .from(sessionTransferToken)
.innerJoin( .where(eq(sessionTransferToken.token, tokenRaw))
sessions, .innerJoin(
eq(sessions.sessionId, sessionTransferToken.sessionId) sessions,
) eq(sessions.sessionId, sessionTransferToken.sessionId)
.limit(1); )
.limit(1)
);
const [existing] = result;
if (!existing) { if (!existing) {
return next( return next(
@@ -45,7 +45,7 @@ const getOrgSchema = z.strictObject({
// content: { // content: {
// "application/json": { // "application/json": {
// schema: z.object({ // schema: z.object({
// data: z.unknown().nullable(), // data: z.record(z.string(), z.any()).nullable(),
// success: z.boolean(), // success: z.boolean(),
// error: z.boolean(), // error: z.boolean(),
// message: z.string(), // message: z.string(),
@@ -1,187 +0,0 @@
/*
* 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"
)
);
}
}
@@ -1,130 +0,0 @@
/*
* 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"
)
);
}
}
@@ -1,109 +0,0 @@
/*
* 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"
)
);
}
}
@@ -13,9 +13,8 @@
import { Request, Response, NextFunction } from "express"; import { Request, Response, NextFunction } from "express";
import { z } from "zod"; import { z } from "zod";
import { browserGatewayTarget, db } from "@server/db"; import { db, resources, targets } from "@server/db";
import { resources, targets } from "@server/db"; import { eq, and, inArray } from "drizzle-orm";
import { eq } from "drizzle-orm";
import response from "@server/lib/response"; import response from "@server/lib/response";
import HttpCode from "@server/types/HttpCode"; import HttpCode from "@server/types/HttpCode";
import createHttpError from "http-errors"; import createHttpError from "http-errors";
@@ -51,31 +50,30 @@ export async function getBrowserTarget(
logger.info(`Retrieving browser target for domain: ${fullDomain}`); logger.info(`Retrieving browser target for domain: ${fullDomain}`);
const [browserTarget] = await db const [row] = await db
.select({ .select({
destination: browserGatewayTarget.destination, ip: targets.ip,
destinationPort: browserGatewayTarget.destinationPort, port: targets.port,
authToken: browserGatewayTarget.authToken, authToken: targets.authToken,
resourceId: resources.resourceId, resourceId: resources.resourceId,
niceId: resources.niceId, niceId: resources.niceId,
name: resources.name,
orgId: resources.orgId, orgId: resources.orgId,
pamMode: resources.pamMode, pamMode: resources.pamMode,
authDaemonMode: resources.authDaemonMode authDaemonMode: resources.authDaemonMode
}) })
.from(browserGatewayTarget) .from(targets)
.innerJoin( .innerJoin(resources, eq(targets.resourceId, resources.resourceId))
resources, .where(
eq(browserGatewayTarget.resourceId, resources.resourceId) and(
eq(resources.fullDomain, fullDomain),
eq(targets.enabled, true),
inArray(targets.mode, ["ssh", "rdp", "vnc"])
)
) )
.where(eq(resources.fullDomain, fullDomain))
.limit(1); .limit(1);
const decryptedAuthToken = decrypt( if (!row) {
browserTarget.authToken,
config.getRawConfig().server.secret!
);
if (!browserTarget) {
return next( return next(
createHttpError( createHttpError(
HttpCode.NOT_FOUND, HttpCode.NOT_FOUND,
@@ -84,16 +82,21 @@ export async function getBrowserTarget(
); );
} }
const decryptedAuthToken = row.authToken
? decrypt(row.authToken, config.getRawConfig().server.secret!)
: "";
return response<GetBrowserTargetResponse>(res, { return response<GetBrowserTargetResponse>(res, {
data: { data: {
ip: browserTarget.destination, ip: row.ip,
port: browserTarget.destinationPort, port: row.port,
authToken: decryptedAuthToken, authToken: decryptedAuthToken,
pamMode: browserTarget.pamMode, pamMode: row.pamMode,
authDaemonMode: browserTarget.authDaemonMode, authDaemonMode: row.authDaemonMode,
orgId: browserTarget.orgId, orgId: row.orgId,
resourceId: browserTarget.resourceId, resourceId: row.resourceId,
niceId: browserTarget.niceId niceId: row.niceId,
name: row.name ?? ""
}, },
success: true, success: true,
error: false, error: false,
@@ -11,9 +11,4 @@
* This file is not licensed under the AGPLv3. * This file is not licensed under the AGPLv3.
*/ */
export * from "./createBrowserGatewayTarget";
export * from "./updateBrowserGatewayTarget";
export * from "./deleteBrowserGatewayTarget";
export * from "./getBrowserGatewayTarget";
export * from "./listBrowserGatewayTargets";
export * from "./getBrowserTarget"; export * from "./getBrowserTarget";
@@ -1,159 +0,0 @@
/*
* 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 rows = await db
.select({
browserGatewayTargetId:
browserGatewayTarget.browserGatewayTargetId,
resourceId: browserGatewayTarget.resourceId,
siteId: browserGatewayTarget.siteId,
authToken: browserGatewayTarget.authToken,
type: browserGatewayTarget.type,
destination: browserGatewayTarget.destination,
destinationPort: browserGatewayTarget.destinationPort,
siteName: sites.name
})
.from(browserGatewayTarget)
.leftJoin(sites, eq(sites.siteId, browserGatewayTarget.siteId))
.where(eq(browserGatewayTarget.resourceId, resourceId))
.limit(limit)
.offset(offset);
return response<ListBrowserGatewayTargetsResponse>(res, {
data: {
targets: rows as any,
total: rows.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"
)
);
}
}
@@ -1,180 +0,0 @@
/*
* 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"
)
);
}
}
@@ -121,7 +121,7 @@ registry.registerPath({
content: { content: {
"application/json": { "application/json": {
schema: z.object({ schema: z.object({
data: z.unknown().nullable(), data: z.record(z.string(), z.any()).nullable(),
success: z.boolean(), success: z.boolean(),
error: z.boolean(), error: z.boolean(),
message: z.string(), message: z.string(),
@@ -46,7 +46,7 @@ registry.registerPath({
content: { content: {
"application/json": { "application/json": {
schema: z.object({ schema: z.object({
data: z.unknown().nullable(), data: z.record(z.string(), z.any()).nullable(),
success: z.boolean(), success: z.boolean(),
error: z.boolean(), error: z.boolean(),
message: z.string(), message: z.string(),
@@ -29,7 +29,7 @@ import { tierMatrix } from "@server/lib/billing/tierMatrix";
const paramsSchema = z.strictObject({}); const paramsSchema = z.strictObject({});
const querySchema = z.strictObject({ const querySchema = z.strictObject({
subdomain: z.string(), subdomain: z.string()
// orgId: build === "saas" ? z.string() : z.string().optional() // Required for saas, optional otherwise // orgId: build === "saas" ? z.string() : z.string().optional() // Required for saas, optional otherwise
}); });
@@ -48,7 +48,7 @@ registry.registerPath({
content: { content: {
"application/json": { "application/json": {
schema: z.object({ schema: z.object({
data: z.unknown().nullable(), data: z.record(z.string(), z.any()).nullable(),
success: z.boolean(), success: z.boolean(),
error: z.boolean(), error: z.boolean(),
message: z.string(), message: z.string(),
@@ -33,7 +33,8 @@ const paramsSchema = z
registry.registerPath({ registry.registerPath({
method: "delete", method: "delete",
path: "/org/{orgId}/event-streaming-destination/{destinationId}", path: "/org/{orgId}/event-streaming-destination/{destinationId}",
description: "Delete an event streaming destination for a specific organization.", description:
"Delete an event streaming destination for a specific organization.",
tags: [OpenAPITags.Org], tags: [OpenAPITags.Org],
request: { request: {
params: paramsSchema params: paramsSchema
@@ -44,7 +45,7 @@ registry.registerPath({
content: { content: {
"application/json": { "application/json": {
schema: z.object({ schema: z.object({
data: z.unknown().nullable(), data: z.record(z.string(), z.any()).nullable(),
success: z.boolean(), success: z.boolean(),
error: z.boolean(), error: z.boolean(),
message: z.string(), message: z.string(),
@@ -115,4 +116,4 @@ export async function deleteEventStreamingDestination(
createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred") createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred")
); );
} }
} }
-46
View File
@@ -31,7 +31,6 @@ import * as siteProvisioning from "#private/routers/siteProvisioning";
import * as eventStreamingDestination from "#private/routers/eventStreamingDestination"; import * as eventStreamingDestination from "#private/routers/eventStreamingDestination";
import * as alertRule from "#private/routers/alertRule"; import * as alertRule from "#private/routers/alertRule";
import * as healthChecks from "#private/routers/healthChecks"; import * as healthChecks from "#private/routers/healthChecks";
import * as browserGatewayTarget from "#private/routers/browserGatewayTarget";
import * as labels from "#private/routers/labels"; import * as labels from "#private/routers/labels";
import * as client from "@server/routers/client"; import * as client from "@server/routers/client";
import * as resource from "#private/routers/resource"; import * as resource from "#private/routers/resource";
@@ -879,48 +878,3 @@ authenticated.post(
verifyClientAccess, verifyClientAccess,
client.rebuildClientAssociationsCacheRoute 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
);
@@ -47,7 +47,7 @@ registry.registerPath({
content: { content: {
"application/json": { "application/json": {
schema: z.object({ schema: z.object({
data: z.unknown().nullable(), data: z.record(z.string(), z.any()).nullable(),
success: z.boolean(), success: z.boolean(),
error: z.boolean(), error: z.boolean(),
message: z.string(), message: z.string(),
@@ -74,7 +74,7 @@ registry.registerPath({
content: { content: {
"application/json": { "application/json": {
schema: z.object({ schema: z.object({
data: z.unknown().nullable(), data: z.record(z.string(), z.any()).nullable(),
success: z.boolean(), success: z.boolean(),
error: z.boolean(), error: z.boolean(),
message: z.string(), message: z.string(),
+32 -6
View File
@@ -79,7 +79,10 @@ import logger from "@server/logger";
import { decrypt } from "@server/lib/crypto"; import { decrypt } from "@server/lib/crypto";
import config from "@server/lib/config"; import config from "@server/lib/config";
import { exchangeSession } from "@server/routers/badger"; import { exchangeSession } from "@server/routers/badger";
import { validateResourceSessionToken } from "@server/auth/sessions/resource"; import {
ResourceSessionValidationResult,
validateResourceSessionToken
} from "@server/auth/sessions/resource";
import { checkExitNodeOrg, resolveExitNodes } from "#private/lib/exitNodes"; import { checkExitNodeOrg, resolveExitNodes } from "#private/lib/exitNodes";
import { maxmindLookup } from "@server/db/maxmind"; import { maxmindLookup } from "@server/db/maxmind";
import { verifyResourceAccessToken } from "@server/auth/verifyResourceAccessToken"; import { verifyResourceAccessToken } from "@server/auth/verifyResourceAccessToken";
@@ -216,9 +219,9 @@ export type ResourceWithAuth = {
password: ResourcePassword | ResourcePolicyPassword | null; password: ResourcePassword | ResourcePolicyPassword | null;
headerAuth: ResourceHeaderAuth | ResourcePolicyHeaderAuth | null; headerAuth: ResourceHeaderAuth | ResourcePolicyHeaderAuth | null;
headerAuthExtendedCompatibility: ResourceHeaderAuthExtendedCompatibility | null; headerAuthExtendedCompatibility: ResourceHeaderAuthExtendedCompatibility | null;
applyRules: boolean; applyRules: boolean | null;
sso: boolean; sso: boolean | null;
emailWhitelistEnabled: boolean; emailWhitelistEnabled: boolean | null;
org: Org; org: Org;
}; };
@@ -1754,11 +1757,34 @@ hybridRouter.post(
resourceId resourceId
); );
// this is for backward compatibility with nodes that did not have the policy id checking
const modifiedResult: ResourceSessionValidationResult = {
...result,
resourceSession: result.resourceSession
? {
...result.resourceSession,
// Prefer policy IDs, but keep legacy IDs populated for older nodes.
pincodeId:
result.resourceSession.policyPincodeId ??
result.resourceSession.pincodeId ??
null,
passwordId:
result.resourceSession.policyPasswordId ??
result.resourceSession.passwordId ??
null,
whitelistId:
result.resourceSession.policyWhitelistId ??
result.resourceSession.whitelistId ??
null
}
: null
};
return response(res, { return response(res, {
data: result, data: modifiedResult,
success: true, success: true,
error: false, error: false,
message: result.resourceSession message: modifiedResult.resourceSession
? "Resource session token is valid" ? "Resource session token is valid"
: "Resource session token is invalid or expired", : "Resource session token is invalid or expired",
status: HttpCode.OK status: HttpCode.OK
-41
View File
@@ -16,7 +16,6 @@ import * as org from "#private/routers/org";
import * as logs from "#private/routers/auditLogs"; import * as logs from "#private/routers/auditLogs";
import * as alertEvents from "#private/routers/alertEvents"; import * as alertEvents from "#private/routers/alertEvents";
import * as certificates from "#private/routers/certificates"; import * as certificates from "#private/routers/certificates";
import * as browserGatewayTarget from "#private/routers/browserGatewayTarget";
import { import {
verifyApiKeyHasAction, verifyApiKeyHasAction,
@@ -216,43 +215,3 @@ authenticated.delete(
logActionAudit(ActionsEnum.removeUserRole), logActionAudit(ActionsEnum.removeUserRole),
user.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
);
+1 -1
View File
@@ -17,9 +17,9 @@ import * as orgIdp from "#private/routers/orgIdp";
import * as billing from "#private/routers/billing"; import * as billing from "#private/routers/billing";
import * as license from "#private/routers/license"; import * as license from "#private/routers/license";
import * as resource from "#private/routers/resource"; import * as resource from "#private/routers/resource";
import * as browserTarget from "#private/routers/browserGatewayTarget";
import * as ssh from "#private/routers/ssh"; import * as ssh from "#private/routers/ssh";
import * as ws from "@server/routers/ws"; import * as ws from "@server/routers/ws";
import * as browserTarget from "#private/routers/browserGatewayTarget";
import { import {
verifySessionUserMiddleware, verifySessionUserMiddleware,
@@ -22,7 +22,7 @@ import response from "@server/lib/response";
import logger from "@server/logger"; import logger from "@server/logger";
import type { CreateOrEditLabelResponse } from "@server/routers/labels/types"; import type { CreateOrEditLabelResponse } from "@server/routers/labels/types";
import HttpCode from "@server/types/HttpCode"; import HttpCode from "@server/types/HttpCode";
import { and, eq } from "drizzle-orm"; import { and, eq, sql } from "drizzle-orm";
import { NextFunction, Request, Response } from "express"; import { NextFunction, Request, Response } from "express";
import createHttpError from "http-errors"; import createHttpError from "http-errors";
import { z } from "zod"; import { z } from "zod";
@@ -107,6 +107,26 @@ export async function createOrgLabel(
} }
} }
const [existingLabel] = await db
.select({ labelId: labels.labelId })
.from(labels)
.where(
and(
eq(labels.orgId, orgId),
sql`LOWER(${labels.name}) = ${name.toLowerCase()}`
)
)
.limit(1);
if (existingLabel) {
return next(
createHttpError(
HttpCode.CONFLICT,
"A label with this name already exists"
)
);
}
const label = await db.transaction(async (tx) => { const label = await db.transaction(async (tx) => {
const [label] = await tx const [label] = await tx
.insert(labels) .insert(labels)
@@ -16,7 +16,7 @@ import response from "@server/lib/response";
import logger from "@server/logger"; import logger from "@server/logger";
import type { CreateOrEditLabelResponse } from "@server/routers/labels/types"; import type { CreateOrEditLabelResponse } from "@server/routers/labels/types";
import HttpCode from "@server/types/HttpCode"; import HttpCode from "@server/types/HttpCode";
import { and, eq } from "drizzle-orm"; import { and, eq, ne, sql } from "drizzle-orm";
import { NextFunction, Request, Response } from "express"; import { NextFunction, Request, Response } from "express";
import createHttpError from "http-errors"; import createHttpError from "http-errors";
import { z } from "zod"; import { z } from "zod";
@@ -74,6 +74,29 @@ export async function updateOrgLabel(
const { name, color } = parsedBody.data; const { name, color } = parsedBody.data;
if (name && name.toLowerCase() !== existing.name.toLowerCase()) {
const [duplicateLabel] = await db
.select({ labelId: labels.labelId })
.from(labels)
.where(
and(
eq(labels.orgId, orgId),
ne(labels.labelId, labelId),
sql`LOWER(${labels.name}) = ${name.toLowerCase()}`
)
)
.limit(1);
if (duplicateLabel) {
return next(
createHttpError(
HttpCode.CONFLICT,
"A label with this name already exists"
)
);
}
}
const [label] = await db const [label] = await db
.update(labels) .update(labels)
.set({ .set({
@@ -69,7 +69,7 @@ registry.registerPath({
content: { content: {
"application/json": { "application/json": {
schema: z.object({ schema: z.object({
data: z.unknown().nullable(), data: z.record(z.string(), z.any()).nullable(),
success: z.boolean(), success: z.boolean(),
error: z.boolean(), error: z.boolean(),
message: z.string(), message: z.string(),
@@ -127,7 +127,8 @@ export async function createOrgOidcIdp(
let { autoProvision } = parsedBody.data; let { autoProvision } = parsedBody.data;
if (build == "saas") { // this is not paywalled with a ee license because this whole endpoint is restricted if (build == "saas") {
// this is not paywalled with a ee license because this whole endpoint is restricted
const subscribed = await isSubscribed( const subscribed = await isSubscribed(
orgId, orgId,
tierMatrix.deviceApprovals tierMatrix.deviceApprovals
@@ -44,7 +44,7 @@ registry.registerPath({
content: { content: {
"application/json": { "application/json": {
schema: z.object({ schema: z.object({
data: z.unknown().nullable(), data: z.record(z.string(), z.any()).nullable(),
success: z.boolean(), success: z.boolean(),
error: z.boolean(), error: z.boolean(),
message: z.string(), message: z.string(),
+1 -1
View File
@@ -62,7 +62,7 @@ registry.registerPath({
content: { content: {
"application/json": { "application/json": {
schema: z.object({ schema: z.object({
data: z.unknown().nullable(), data: z.record(z.string(), z.any()).nullable(),
success: z.boolean(), success: z.boolean(),
error: z.boolean(), error: z.boolean(),
message: z.string(), message: z.string(),
+1 -1
View File
@@ -78,7 +78,7 @@ registry.registerPath({
content: { content: {
"application/json": { "application/json": {
schema: z.object({ schema: z.object({
data: z.unknown().nullable(), data: z.record(z.string(), z.any()).nullable(),
success: z.boolean(), success: z.boolean(),
error: z.boolean(), error: z.boolean(),
message: z.string(), message: z.string(),
@@ -33,9 +33,8 @@ import {
import { getUniqueResourcePolicyName } from "@server/db/names"; import { getUniqueResourcePolicyName } from "@server/db/names";
import response from "@server/lib/response"; import response from "@server/lib/response";
import { import {
isValidCIDR, getResourceRuleValueValidationError,
isValidIP, RESOURCE_RULE_MATCH_TYPES
isValidUrlGlobPattern
} from "@server/lib/validators"; } from "@server/lib/validators";
import logger from "@server/logger"; import logger from "@server/logger";
import { OpenAPITags, registry } from "@server/openApi"; import { OpenAPITags, registry } from "@server/openApi";
@@ -56,9 +55,9 @@ const ruleSchema = z.strictObject({
enum: ["ACCEPT", "DROP", "PASS"], enum: ["ACCEPT", "DROP", "PASS"],
description: "rule action" description: "rule action"
}), }),
match: z.enum(["CIDR", "IP", "PATH"]).openapi({ match: z.enum(RESOURCE_RULE_MATCH_TYPES).openapi({
type: "string", type: "string",
enum: ["CIDR", "IP", "PATH"], enum: [...RESOURCE_RULE_MATCH_TYPES],
description: "rule match" description: "rule match"
}), }),
value: z.string().min(1), value: z.string().min(1),
@@ -261,26 +260,13 @@ export async function createResourcePolicy(
const niceId = await getUniqueResourcePolicyName(orgId); const niceId = await getUniqueResourcePolicyName(orgId);
for (const rule of rules) { for (const rule of rules) {
if (rule.match === "CIDR" && !isValidCIDR(rule.value)) { const validationError = getResourceRuleValueValidationError(
rule.match,
rule.value
);
if (validationError) {
return next( return next(
createHttpError( createHttpError(HttpCode.BAD_REQUEST, validationError)
HttpCode.BAD_REQUEST,
"Invalid CIDR provided"
)
);
} else if (rule.match === "IP" && !isValidIP(rule.value)) {
return next(
createHttpError(HttpCode.BAD_REQUEST, "Invalid IP provided")
);
} else if (
rule.match === "PATH" &&
!isValidUrlGlobPattern(rule.value)
) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
"Invalid URL glob pattern provided"
)
); );
} }
} }
@@ -216,6 +216,7 @@ export async function listResourcePolicies(
: await db : await db
.select({ .select({
resourceId: resources.resourceId, resourceId: resources.resourceId,
niceId: resources.niceId,
name: resources.name, name: resources.name,
fullDomain: resources.fullDomain, fullDomain: resources.fullDomain,
resourcePolicyId: resources.resourcePolicyId resourcePolicyId: resources.resourcePolicyId
@@ -0,0 +1,202 @@
/*
* 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 axios from "axios";
import { db, exitNodes, newts, sites } from "@server/db";
import { eq } from "drizzle-orm";
import logger from "@server/logger";
import redisManager from "#private/lib/redis";
import { sendToClient } from "#private/routers/ws";
const INITIAL_DELAY_MS = 15 * 1000; // 15 seconds before first check
const CHECK_INTERVAL_MS = 10 * 1000; // Check every 10 seconds
const MAX_DURATION_MS = 5 * 60 * 1000; // Give up after 5 minutes
const REDIS_PENDING_SET = "exit-node-reconnect-pending";
const REDIS_HASH_PREFIX = "exit-node-reconnect:";
interface PendingReconnect {
startTime: number;
reachableAt: string;
}
// In-memory tracking for this node
const pendingReconnects = new Map<number, PendingReconnect>();
let schedulerInterval: NodeJS.Timeout | null = null;
/**
* Schedules a reconnect check for newts connected to the given exit node.
* Called when an exit node transitions from offline to online.
*/
export async function scheduleExitNodeReconnect(
exitNodeId: number,
reachableAt: string
): Promise<void> {
logger.info(
`Scheduling newt reconnect for exit node ${exitNodeId} (reachableAt: ${reachableAt})`
);
const entry: PendingReconnect = {
startTime: Date.now(),
reachableAt
};
pendingReconnects.set(exitNodeId, entry);
// Store in Redis if available for cross-node coordination
if (redisManager.isRedisEnabled()) {
await redisManager.sadd(REDIS_PENDING_SET, exitNodeId.toString());
await redisManager.hset(
`${REDIS_HASH_PREFIX}${exitNodeId}`,
"startTime",
entry.startTime.toString()
);
await redisManager.hset(
`${REDIS_HASH_PREFIX}${exitNodeId}`,
"reachableAt",
reachableAt
);
}
}
/**
* Starts the background interval that checks pending exit node reconnects.
*/
export function startExitNodeReconnectScheduler(): void {
if (schedulerInterval) {
return;
}
schedulerInterval = setInterval(async () => {
try {
await processPendingReconnects();
} catch (error) {
logger.error("Error in exit node reconnect scheduler", { error });
}
}, CHECK_INTERVAL_MS);
logger.debug("Started exit node reconnect scheduler");
}
async function processPendingReconnects(): Promise<void> {
// Merge in-memory and Redis-tracked pending reconnects
const toProcess = new Map(pendingReconnects);
if (redisManager.isRedisEnabled()) {
const redisIds = await redisManager.smembers(REDIS_PENDING_SET);
for (const idStr of redisIds) {
const id = parseInt(idStr, 10);
if (!toProcess.has(id)) {
const startTimeStr = await redisManager.hget(
`${REDIS_HASH_PREFIX}${id}`,
"startTime"
);
const reachableAt = await redisManager.hget(
`${REDIS_HASH_PREFIX}${id}`,
"reachableAt"
);
if (startTimeStr && reachableAt) {
toProcess.set(id, {
startTime: parseInt(startTimeStr, 10),
reachableAt
});
}
}
}
}
const now = Date.now();
for (const [exitNodeId, entry] of toProcess) {
const elapsed = now - entry.startTime;
// Give up after max duration
if (elapsed >= MAX_DURATION_MS) {
logger.warn(
`Exit node reconnect check timed out for exit node ${exitNodeId} after 5 minutes`
);
await removePending(exitNodeId);
continue;
}
// Respect initial delay
if (elapsed < INITIAL_DELAY_MS) {
continue;
}
// Check if the exit node HTTP endpoint is reachable
const pingUrl = `${entry.reachableAt}/ping`;
try {
await axios.get(pingUrl, { timeout: 5000 });
} catch {
logger.debug(
`Exit node ${exitNodeId} not yet reachable at ${pingUrl}`
);
continue;
}
// Node is reachable — send reconnect to all connected newts
logger.info(
`Exit node ${exitNodeId} is reachable. Sending newt/wg/reconnect to connected newts.`
);
await sendReconnectToNewts(exitNodeId);
await removePending(exitNodeId);
}
}
async function sendReconnectToNewts(exitNodeId: number): Promise<void> {
try {
const connectedNewts = await db
.select({ newtId: newts.newtId })
.from(newts)
.innerJoin(sites, eq(newts.siteId, sites.siteId))
.where(eq(sites.exitNodeId, exitNodeId));
if (connectedNewts.length === 0) {
logger.debug(
`No newts found for exit node ${exitNodeId}, nothing to reconnect`
);
return;
}
logger.info(
`Sending newt/wg/reconnect to ${connectedNewts.length} newt(s) for exit node ${exitNodeId}`
);
const reconnectMessage = {
type: "newt/wg/reconnect",
data: {}
};
await Promise.allSettled(
connectedNewts.map(({ newtId }) =>
sendToClient(newtId, reconnectMessage)
)
);
} catch (error) {
logger.error(
`Failed to send reconnect messages for exit node ${exitNodeId}`,
{ error }
);
}
}
async function removePending(exitNodeId: number): Promise<void> {
pendingReconnects.delete(exitNodeId);
if (redisManager.isRedisEnabled()) {
await redisManager.srem(REDIS_PENDING_SET, exitNodeId.toString());
await redisManager.del(`${REDIS_HASH_PREFIX}${exitNodeId}`);
}
}
@@ -16,6 +16,7 @@ import { MessageHandler } from "@server/routers/ws";
import { RemoteExitNode } from "@server/db"; import { RemoteExitNode } from "@server/db";
import { eq } from "drizzle-orm"; import { eq } from "drizzle-orm";
import logger from "@server/logger"; import logger from "@server/logger";
import { scheduleExitNodeReconnect } from "./exitNodeReconnectScheduler";
/** /**
* Handles ping messages from clients and responds with pong * Handles ping messages from clients and responds with pong
@@ -37,6 +38,13 @@ export const handleRemoteExitNodePingMessage: MessageHandler = async (
} }
try { try {
// Fetch the current state before updating so we can detect the offline→online transition
const [currentExitNode] = await db
.select({ online: exitNodes.online, reachableAt: exitNodes.reachableAt })
.from(exitNodes)
.where(eq(exitNodes.exitNodeId, remoteExitNode.exitNodeId))
.limit(1);
// Update the exit node's last ping timestamp // Update the exit node's last ping timestamp
await db await db
.update(exitNodes) .update(exitNodes)
@@ -45,6 +53,16 @@ export const handleRemoteExitNodePingMessage: MessageHandler = async (
online: true online: true
}) })
.where(eq(exitNodes.exitNodeId, remoteExitNode.exitNodeId)); .where(eq(exitNodes.exitNodeId, remoteExitNode.exitNodeId));
// If the exit node was offline and is now coming online, schedule newt reconnects
if (currentExitNode && !currentExitNode.online && currentExitNode.reachableAt) {
scheduleExitNodeReconnect(
remoteExitNode.exitNodeId,
currentExitNode.reachableAt
).catch((error) => {
logger.error("Failed to schedule exit node reconnect", { error });
});
}
} catch (error) { } catch (error) {
logger.error("Error handling ping message", { error }); logger.error("Error handling ping message", { error });
} }
@@ -22,3 +22,4 @@ export * from "./listRemoteExitNodes";
export * from "./pickRemoteExitNodeDefaults"; export * from "./pickRemoteExitNodeDefaults";
export * from "./quickStartRemoteExitNode"; export * from "./quickStartRemoteExitNode";
export * from "./offlineChecker"; export * from "./offlineChecker";
export * from "./exitNodeReconnectScheduler";
+9 -10
View File
@@ -12,6 +12,7 @@
*/ */
import { Request, Response, NextFunction } from "express"; import { Request, Response, NextFunction } from "express";
import { randomInt } from "crypto";
import { z } from "zod"; import { z } from "zod";
import { import {
actionAuditLog, actionAuditLog,
@@ -29,8 +30,7 @@ import {
userOrgs, userOrgs,
sites, sites,
Resource, Resource,
SiteResource, SiteResource
browserGatewayTarget
} from "@server/db"; } from "@server/db";
import { logAccessAudit } from "#private/lib/logAccessAudit"; import { logAccessAudit } from "#private/lib/logAccessAudit";
import { isLicensedOrSubscribed } from "#private/lib/isLicencedOrSubscribed"; import { isLicensedOrSubscribed } from "#private/lib/isLicencedOrSubscribed";
@@ -290,16 +290,15 @@ export async function signSshKey(
const publicResource = resource as Resource; const publicResource = resource as Resource;
const targetRows = await db const targetRows = await db
.select({ .select({
siteId: browserGatewayTarget.siteId, siteId: targets.siteId,
ip: browserGatewayTarget.destination ip: targets.ip
}) })
.from(browserGatewayTarget) .from(targets)
.where( .where(
and( and(
eq( eq(targets.resourceId, publicResource.resourceId),
browserGatewayTarget.resourceId, eq(targets.enabled, true),
publicResource.resourceId eq(targets.mode, "ssh")
)
) )
); );
@@ -392,7 +391,7 @@ export async function signSshKey(
if (existingUserWithSameName) { if (existingUserWithSameName) {
let foundUniqueUsername = false; let foundUniqueUsername = false;
for (let attempt = 0; attempt < 20; attempt++) { for (let attempt = 0; attempt < 20; attempt++) {
const randomNum = Math.floor(Math.random() * 101); // 0 to 100 const randomNum = randomInt(0, 101); // 0 to 100
const candidateUsername = `${usernameToUse}${randomNum}`; const candidateUsername = `${usernameToUse}${randomNum}`;
const [existingUser] = await db const [existingUser] = await db
+1 -1
View File
@@ -44,7 +44,7 @@ registry.registerPath({
content: { content: {
"application/json": { "application/json": {
schema: z.object({ schema: z.object({
data: z.unknown().nullable(), data: z.record(z.string(), z.any()).nullable(),
success: z.boolean(), success: z.boolean(),
error: z.boolean(), error: z.boolean(),
message: z.string(), message: z.string(),
@@ -45,7 +45,7 @@ registry.registerPath({
content: { content: {
"application/json": { "application/json": {
schema: z.object({ schema: z.object({
data: z.unknown().nullable(), data: z.record(z.string(), z.any()).nullable(),
success: z.boolean(), success: z.boolean(),
error: z.boolean(), error: z.boolean(),
message: z.string(), message: z.string(),
+3 -1
View File
@@ -14,7 +14,8 @@
import { import {
handleRemoteExitNodeRegisterMessage, handleRemoteExitNodeRegisterMessage,
handleRemoteExitNodePingMessage, handleRemoteExitNodePingMessage,
startRemoteExitNodeOfflineChecker startRemoteExitNodeOfflineChecker,
startExitNodeReconnectScheduler
} from "#private/routers/remoteExitNode"; } from "#private/routers/remoteExitNode";
import { MessageHandler } from "@server/routers/ws"; import { MessageHandler } from "@server/routers/ws";
import { build } from "@server/build"; import { build } from "@server/build";
@@ -29,4 +30,5 @@ export const messageHandlers: Record<string, MessageHandler> = {
if (build != "saas") { if (build != "saas") {
startRemoteExitNodeOfflineChecker(); // this is to handle the offline check for remote exit nodes startRemoteExitNodeOfflineChecker(); // this is to handle the offline check for remote exit nodes
startExitNodeReconnectScheduler(); // check pending exit node reconnects and notify newts
} }
@@ -28,7 +28,7 @@ registry.registerPath({
content: { content: {
"application/json": { "application/json": {
schema: z.object({ schema: z.object({
data: z.unknown().nullable(), data: z.record(z.string(), z.any()).nullable(),
success: z.boolean(), success: z.boolean(),
error: z.boolean(), error: z.boolean(),
message: z.string(), message: z.string(),
@@ -61,7 +61,7 @@ registry.registerPath({
content: { content: {
"application/json": { "application/json": {
schema: z.object({ schema: z.object({
data: z.unknown().nullable(), data: z.record(z.string(), z.any()).nullable(),
success: z.boolean(), success: z.boolean(),
error: z.boolean(), error: z.boolean(),
message: z.string(), message: z.string(),
@@ -135,7 +135,7 @@ registry.registerPath({
content: { content: {
"application/json": { "application/json": {
schema: z.object({ schema: z.object({
data: z.unknown().nullable(), data: z.record(z.string(), z.any()).nullable(),
success: z.boolean(), success: z.boolean(),
error: z.boolean(), error: z.boolean(),
message: z.string(), message: z.string(),
@@ -164,7 +164,7 @@ registry.registerPath({
content: { content: {
"application/json": { "application/json": {
schema: z.object({ schema: z.object({
data: z.unknown().nullable(), data: z.record(z.string(), z.any()).nullable(),
success: z.boolean(), success: z.boolean(),
error: z.boolean(), error: z.boolean(),
message: z.string(), message: z.string(),
+1 -1
View File
@@ -28,7 +28,7 @@ registry.registerPath({
content: { content: {
"application/json": { "application/json": {
schema: z.object({ schema: z.object({
data: z.unknown().nullable(), data: z.record(z.string(), z.any()).nullable(),
success: z.boolean(), success: z.boolean(),
error: z.boolean(), error: z.boolean(),
message: z.string(), message: z.string(),
+1 -1
View File
@@ -42,7 +42,7 @@ registry.registerPath({
content: { content: {
"application/json": { "application/json": {
schema: z.object({ schema: z.object({
data: z.unknown().nullable(), data: z.record(z.string(), z.any()).nullable(),
success: z.boolean(), success: z.boolean(),
error: z.boolean(), error: z.boolean(),
message: z.string(), message: z.string(),
@@ -35,7 +35,7 @@ registry.registerPath({
content: { content: {
"application/json": { "application/json": {
schema: z.object({ schema: z.object({
data: z.unknown().nullable(), data: z.record(z.string(), z.any()).nullable(),
success: z.boolean(), success: z.boolean(),
error: z.boolean(), error: z.boolean(),
message: z.string(), message: z.string(),

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