Compare commits

...

203 Commits

Author SHA1 Message Date
Owen Schwartz
23cf7bf745 New translations en-us.json (Norwegian Bokmal) 2026-01-15 21:54:41 -08:00
Owen Schwartz
d63de9ba40 New translations en-us.json (Chinese Simplified) 2026-01-15 21:54:40 -08:00
Owen Schwartz
e7ac5c34a2 New translations en-us.json (Turkish) 2026-01-15 21:54:38 -08:00
Owen Schwartz
1f88876e3c New translations en-us.json (Russian) 2026-01-15 21:54:37 -08:00
Owen Schwartz
e0425e2458 New translations en-us.json (Portuguese) 2026-01-15 21:54:36 -08:00
Owen Schwartz
10e9f017fb New translations en-us.json (Polish) 2026-01-15 21:54:34 -08:00
Owen Schwartz
6d2cb69e45 New translations en-us.json (Dutch) 2026-01-15 21:54:33 -08:00
Owen Schwartz
2fab9b65a8 New translations en-us.json (Korean) 2026-01-15 21:54:31 -08:00
Owen Schwartz
47a7a3f230 New translations en-us.json (Italian) 2026-01-15 21:54:30 -08:00
Owen Schwartz
61835ca0e6 New translations en-us.json (German) 2026-01-15 21:54:29 -08:00
Owen Schwartz
f4b22c5b31 New translations en-us.json (Czech) 2026-01-15 21:54:27 -08:00
Owen Schwartz
65afe23dd8 New translations en-us.json (Bulgarian) 2026-01-15 21:54:26 -08:00
Owen Schwartz
371e44e235 New translations en-us.json (Spanish) 2026-01-15 21:54:25 -08:00
Owen Schwartz
d349795995 New translations en-us.json (French) 2026-01-15 21:54:23 -08:00
Owen Schwartz
7d106294bc New translations en-us.json (Norwegian Bokmal) 2026-01-14 19:55:40 -08:00
Owen Schwartz
8c3e094534 New translations en-us.json (Chinese Simplified) 2026-01-14 19:55:38 -08:00
Owen Schwartz
b20a7231bc New translations en-us.json (Turkish) 2026-01-14 19:55:37 -08:00
Owen Schwartz
84998fdb6e New translations en-us.json (Russian) 2026-01-14 19:55:36 -08:00
Owen Schwartz
0d9ece1329 New translations en-us.json (Portuguese) 2026-01-14 19:55:34 -08:00
Owen Schwartz
eb7107016b New translations en-us.json (Polish) 2026-01-14 19:55:33 -08:00
Owen Schwartz
a7608424b7 New translations en-us.json (Dutch) 2026-01-14 19:55:31 -08:00
Owen Schwartz
6710657789 New translations en-us.json (Korean) 2026-01-14 19:55:30 -08:00
Owen Schwartz
b25069ab49 New translations en-us.json (Italian) 2026-01-14 19:55:29 -08:00
Owen Schwartz
6e8d1f9149 New translations en-us.json (German) 2026-01-14 19:55:27 -08:00
Owen Schwartz
825da82e56 New translations en-us.json (Czech) 2026-01-14 19:55:26 -08:00
Owen Schwartz
8d28063724 New translations en-us.json (Bulgarian) 2026-01-14 19:55:25 -08:00
Owen Schwartz
a7eed886ab New translations en-us.json (Spanish) 2026-01-14 19:55:23 -08:00
Owen Schwartz
4e965f1e83 New translations en-us.json (French) 2026-01-14 19:55:22 -08:00
Owen Schwartz
ea3011afca New translations en-us.json (Norwegian Bokmal) 2026-01-13 15:50:55 -08:00
Owen Schwartz
b20e5b4af1 New translations en-us.json (Chinese Simplified) 2026-01-13 15:50:53 -08:00
Owen Schwartz
577660c656 New translations en-us.json (Turkish) 2026-01-13 15:50:52 -08:00
Owen Schwartz
f9a9007ed9 New translations en-us.json (Russian) 2026-01-13 15:50:50 -08:00
Owen Schwartz
19873a0b3d New translations en-us.json (Portuguese) 2026-01-13 15:50:49 -08:00
Owen Schwartz
4f13267bc8 New translations en-us.json (Polish) 2026-01-13 15:50:47 -08:00
Owen Schwartz
c820fcc518 New translations en-us.json (Dutch) 2026-01-13 15:50:46 -08:00
Owen Schwartz
c612972f38 New translations en-us.json (Korean) 2026-01-13 15:50:45 -08:00
Owen Schwartz
395c4eff53 New translations en-us.json (Italian) 2026-01-13 15:50:44 -08:00
Owen Schwartz
6aa40bfd61 New translations en-us.json (German) 2026-01-13 15:50:42 -08:00
Owen Schwartz
f5744d0dcd New translations en-us.json (Czech) 2026-01-13 15:50:41 -08:00
Owen Schwartz
a975b33b7e New translations en-us.json (Bulgarian) 2026-01-13 15:50:40 -08:00
Owen Schwartz
92be668a55 New translations en-us.json (Spanish) 2026-01-13 15:50:38 -08:00
Owen Schwartz
15153d5e3a New translations en-us.json (French) 2026-01-13 15:50:37 -08:00
Owen Schwartz
212800d365 New translations en-us.json (Norwegian Bokmal) 2026-01-12 21:08:36 -08:00
Owen Schwartz
24ac9a1623 New translations en-us.json (Chinese Simplified) 2026-01-12 21:08:35 -08:00
Owen Schwartz
78bcfed668 New translations en-us.json (Turkish) 2026-01-12 21:08:33 -08:00
Owen Schwartz
bf8be14c3a New translations en-us.json (Russian) 2026-01-12 21:08:32 -08:00
Owen Schwartz
fe1f7bee60 New translations en-us.json (Portuguese) 2026-01-12 21:08:30 -08:00
Owen Schwartz
fbe14acdd1 New translations en-us.json (Polish) 2026-01-12 21:08:29 -08:00
Owen Schwartz
11c0afd9ab New translations en-us.json (Dutch) 2026-01-12 21:08:27 -08:00
Owen Schwartz
52e6b36c2d New translations en-us.json (Korean) 2026-01-12 21:08:26 -08:00
Owen Schwartz
dcce15036c New translations en-us.json (Italian) 2026-01-12 21:08:24 -08:00
Owen Schwartz
fd27682bbb New translations en-us.json (German) 2026-01-12 21:08:23 -08:00
Owen Schwartz
0cffeda5da New translations en-us.json (Czech) 2026-01-12 21:08:21 -08:00
Owen Schwartz
aff486ca57 New translations en-us.json (Bulgarian) 2026-01-12 21:08:20 -08:00
Owen Schwartz
d3eb950888 New translations en-us.json (Spanish) 2026-01-12 21:08:18 -08:00
Owen Schwartz
1b98f44588 New translations en-us.json (French) 2026-01-12 21:08:16 -08:00
Owen Schwartz
8a5a0c7c18 New translations en-us.json (Norwegian Bokmal) 2026-01-12 16:20:12 -08:00
Owen Schwartz
d2f0825498 New translations en-us.json (Chinese Simplified) 2026-01-12 16:20:11 -08:00
Owen Schwartz
ac88b79066 New translations en-us.json (Turkish) 2026-01-12 16:20:09 -08:00
Owen Schwartz
9160d94c7b New translations en-us.json (Russian) 2026-01-12 16:20:08 -08:00
Owen Schwartz
de6ce7aa10 New translations en-us.json (Portuguese) 2026-01-12 16:20:06 -08:00
Owen Schwartz
91a4b11632 New translations en-us.json (Polish) 2026-01-12 16:20:05 -08:00
Owen Schwartz
99ed4ea683 New translations en-us.json (Dutch) 2026-01-12 16:20:04 -08:00
Owen Schwartz
1a0d4870ed New translations en-us.json (Korean) 2026-01-12 16:20:02 -08:00
Owen Schwartz
f1bd315a96 New translations en-us.json (Italian) 2026-01-12 16:20:01 -08:00
Owen Schwartz
42d8e932ff New translations en-us.json (German) 2026-01-12 16:19:59 -08:00
Owen Schwartz
658ec87d1b New translations en-us.json (Czech) 2026-01-12 16:19:58 -08:00
Owen Schwartz
e9bbee13c0 New translations en-us.json (Bulgarian) 2026-01-12 16:19:57 -08:00
Owen Schwartz
99164bf7ab New translations en-us.json (Spanish) 2026-01-12 16:19:55 -08:00
Owen Schwartz
6a3cc578a7 New translations en-us.json (French) 2026-01-12 16:19:54 -08:00
Owen Schwartz
720c3d7c41 New translations en-us.json (German) 2026-01-11 17:43:02 -08:00
Owen Schwartz
72733a9b77 New translations en-us.json (Norwegian Bokmal) 2026-01-11 14:32:28 -08:00
Owen Schwartz
1295141eaf New translations en-us.json (Chinese Simplified) 2026-01-11 14:32:26 -08:00
Owen Schwartz
e8099795b3 New translations en-us.json (Turkish) 2026-01-11 14:32:25 -08:00
Owen Schwartz
d65fbcc28b New translations en-us.json (Russian) 2026-01-11 14:32:24 -08:00
Owen Schwartz
ad0a17f642 New translations en-us.json (Portuguese) 2026-01-11 14:32:23 -08:00
Owen Schwartz
c2ecabab33 New translations en-us.json (Polish) 2026-01-11 14:32:21 -08:00
Owen Schwartz
7170b14b1f New translations en-us.json (Dutch) 2026-01-11 14:32:20 -08:00
Owen Schwartz
343ba64eb4 New translations en-us.json (Korean) 2026-01-11 14:32:19 -08:00
Owen Schwartz
242c314e42 New translations en-us.json (Italian) 2026-01-11 14:32:17 -08:00
Owen Schwartz
16f9191bc5 New translations en-us.json (German) 2026-01-11 14:32:16 -08:00
Owen Schwartz
6ec51e50d1 New translations en-us.json (Czech) 2026-01-11 14:32:15 -08:00
Owen Schwartz
f1d73c5f8c New translations en-us.json (Bulgarian) 2026-01-11 14:32:13 -08:00
Owen Schwartz
6093add470 New translations en-us.json (Spanish) 2026-01-11 14:32:12 -08:00
Owen Schwartz
4af6bd43ef New translations en-us.json (French) 2026-01-11 14:32:11 -08:00
Owen Schwartz
99ad7c6bef New translations en-us.json (Norwegian Bokmal) 2026-01-09 18:42:36 -08:00
Owen Schwartz
fc192d955b New translations en-us.json (Chinese Simplified) 2026-01-09 18:42:35 -08:00
Owen Schwartz
914f1b41c6 New translations en-us.json (Turkish) 2026-01-09 18:42:34 -08:00
Owen Schwartz
0601eacfd5 New translations en-us.json (Russian) 2026-01-09 18:42:33 -08:00
Owen Schwartz
5481eae606 New translations en-us.json (Portuguese) 2026-01-09 18:42:31 -08:00
Owen Schwartz
d868c85ebe New translations en-us.json (Polish) 2026-01-09 18:42:30 -08:00
Owen Schwartz
ff0f0aa4e0 New translations en-us.json (Dutch) 2026-01-09 18:42:29 -08:00
Owen Schwartz
d4f9f20ef9 New translations en-us.json (Korean) 2026-01-09 18:42:27 -08:00
Owen Schwartz
30808ce2db New translations en-us.json (Italian) 2026-01-09 18:42:26 -08:00
Owen Schwartz
db5f7ed731 New translations en-us.json (German) 2026-01-09 18:42:25 -08:00
Owen Schwartz
6fd69f1293 New translations en-us.json (Czech) 2026-01-09 18:42:23 -08:00
Owen Schwartz
9f1dd42a85 New translations en-us.json (Bulgarian) 2026-01-09 18:42:22 -08:00
Owen Schwartz
7b55ef1cc8 New translations en-us.json (Spanish) 2026-01-09 18:42:20 -08:00
Owen Schwartz
b988f21b15 New translations en-us.json (French) 2026-01-09 18:42:19 -08:00
Owen Schwartz
a688757f5c New translations en-us.json (Norwegian Bokmal) 2026-01-09 15:04:27 -08:00
Owen Schwartz
0ed1852a35 New translations en-us.json (Chinese Simplified) 2026-01-09 15:04:26 -08:00
Owen Schwartz
a5c3702b8d New translations en-us.json (Turkish) 2026-01-09 15:04:25 -08:00
Owen Schwartz
b25f43da9a New translations en-us.json (Russian) 2026-01-09 15:04:23 -08:00
Owen Schwartz
ea0f0de802 New translations en-us.json (Portuguese) 2026-01-09 15:04:22 -08:00
Owen Schwartz
476131859d New translations en-us.json (Polish) 2026-01-09 15:04:21 -08:00
Owen Schwartz
8573c38332 New translations en-us.json (Dutch) 2026-01-09 15:04:19 -08:00
Owen Schwartz
bc9d2835be New translations en-us.json (Korean) 2026-01-09 15:04:18 -08:00
Owen Schwartz
df582b0409 New translations en-us.json (Italian) 2026-01-09 15:04:17 -08:00
Owen Schwartz
e3865dcd4d New translations en-us.json (German) 2026-01-09 15:04:15 -08:00
Owen Schwartz
0eba5cdb0b New translations en-us.json (Czech) 2026-01-09 15:04:14 -08:00
Owen Schwartz
2284f2cafc New translations en-us.json (Bulgarian) 2026-01-09 15:04:12 -08:00
Owen Schwartz
80215a7de0 New translations en-us.json (Spanish) 2026-01-09 15:04:11 -08:00
Owen Schwartz
ddd829064a New translations en-us.json (French) 2026-01-09 15:04:10 -08:00
Owen Schwartz
982c692c40 New translations en-us.json (French) 2025-12-24 16:12:11 -05:00
Owen Schwartz
0c3ce7836c New translations en-us.json (Norwegian Bokmal) 2025-12-24 16:12:11 -05:00
Owen Schwartz
7ef86c5707 New translations en-us.json (Chinese Simplified) 2025-12-24 16:12:11 -05:00
Owen Schwartz
f62b88b930 New translations en-us.json (Turkish) 2025-12-24 16:12:11 -05:00
Owen Schwartz
03a326c841 New translations en-us.json (Russian) 2025-12-24 16:12:11 -05:00
Owen Schwartz
4df4cafd70 New translations en-us.json (Portuguese) 2025-12-24 16:12:11 -05:00
Owen Schwartz
4b9539cc6d New translations en-us.json (Polish) 2025-12-24 16:12:11 -05:00
Owen Schwartz
87135c90bd New translations en-us.json (Dutch) 2025-12-24 16:12:11 -05:00
Owen Schwartz
853d416b2f New translations en-us.json (Korean) 2025-12-24 16:12:11 -05:00
Owen Schwartz
bfd14b87bd New translations en-us.json (Italian) 2025-12-24 16:12:11 -05:00
Owen Schwartz
88aba4e169 New translations en-us.json (German) 2025-12-24 16:12:11 -05:00
Owen Schwartz
99e2fcb2e8 New translations en-us.json (Czech) 2025-12-24 16:12:11 -05:00
Owen Schwartz
1f138ab68c New translations en-us.json (Bulgarian) 2025-12-24 16:12:11 -05:00
Owen Schwartz
99ded7454e New translations en-us.json (Spanish) 2025-12-24 16:12:11 -05:00
Owen Schwartz
f82cacac6d New translations en-us.json (French) 2025-12-24 16:12:11 -05:00
Owen Schwartz
a548f61ea6 New translations en-us.json (Norwegian Bokmal) 2025-12-24 16:12:11 -05:00
Owen Schwartz
bfae715076 New translations en-us.json (Chinese Simplified) 2025-12-24 16:12:11 -05:00
Owen Schwartz
358e25b7c2 New translations en-us.json (Turkish) 2025-12-24 16:12:11 -05:00
Owen Schwartz
2c3fa54933 New translations en-us.json (Russian) 2025-12-24 16:12:11 -05:00
Owen Schwartz
00cdd5833e New translations en-us.json (Portuguese) 2025-12-24 16:12:11 -05:00
Owen Schwartz
52b1164e58 New translations en-us.json (Polish) 2025-12-24 16:12:11 -05:00
Owen Schwartz
657bc9cdf0 New translations en-us.json (Dutch) 2025-12-24 16:12:11 -05:00
Owen Schwartz
ec6bcd41b0 New translations en-us.json (Korean) 2025-12-24 16:12:11 -05:00
Owen Schwartz
1721cce040 New translations en-us.json (Italian) 2025-12-24 16:12:11 -05:00
Owen Schwartz
e41a5ad6b0 New translations en-us.json (German) 2025-12-24 16:12:11 -05:00
Owen Schwartz
ee1eca9e66 New translations en-us.json (Czech) 2025-12-24 16:12:11 -05:00
Owen Schwartz
d049369172 New translations en-us.json (Bulgarian) 2025-12-24 16:12:11 -05:00
Owen Schwartz
6280a68d51 New translations en-us.json (Spanish) 2025-12-24 16:12:11 -05:00
Owen Schwartz
32054dc4f6 New translations en-us.json (French) 2025-12-24 16:12:11 -05:00
Owen Schwartz
831c631048 New translations en-us.json (Norwegian Bokmal) 2025-12-24 16:12:11 -05:00
Owen Schwartz
e23711bcce New translations en-us.json (Chinese Simplified) 2025-12-24 16:12:11 -05:00
Owen Schwartz
440bff57d0 New translations en-us.json (Turkish) 2025-12-24 16:12:11 -05:00
Owen Schwartz
7345cc81c1 New translations en-us.json (Russian) 2025-12-24 16:12:11 -05:00
Owen Schwartz
164ab26069 New translations en-us.json (Portuguese) 2025-12-24 16:12:11 -05:00
Owen Schwartz
4b6ace80d3 New translations en-us.json (Polish) 2025-12-24 16:12:11 -05:00
Owen Schwartz
653127a0f7 New translations en-us.json (Dutch) 2025-12-24 16:12:11 -05:00
Owen Schwartz
bf3a1e20fc New translations en-us.json (Korean) 2025-12-24 16:12:11 -05:00
Owen Schwartz
d7a44e7589 New translations en-us.json (Italian) 2025-12-24 16:12:11 -05:00
Owen Schwartz
6c0d583557 New translations en-us.json (Czech) 2025-12-24 16:12:11 -05:00
Owen Schwartz
13f0fb25da New translations en-us.json (Bulgarian) 2025-12-24 16:12:11 -05:00
Owen Schwartz
818aca9ec8 New translations en-us.json (Spanish) 2025-12-24 16:12:11 -05:00
Owen Schwartz
1c7fb476b0 New translations en-us.json (Norwegian Bokmal) 2025-12-24 16:12:11 -05:00
Owen Schwartz
93843ed733 New translations en-us.json (Chinese Simplified) 2025-12-24 16:12:11 -05:00
Owen Schwartz
0973313703 New translations en-us.json (Turkish) 2025-12-24 16:12:11 -05:00
Owen Schwartz
bfbfbe8b11 New translations en-us.json (Russian) 2025-12-24 16:12:11 -05:00
Owen Schwartz
8c62d9fe78 New translations en-us.json (Portuguese) 2025-12-24 16:12:11 -05:00
Owen Schwartz
d5558f55ed New translations en-us.json (Polish) 2025-12-24 16:12:11 -05:00
Owen Schwartz
a96ad6bd07 New translations en-us.json (Dutch) 2025-12-24 16:12:11 -05:00
Owen Schwartz
00d9482a99 New translations en-us.json (Korean) 2025-12-24 16:12:11 -05:00
Owen Schwartz
0f90e2a30f New translations en-us.json (Italian) 2025-12-24 16:12:11 -05:00
Owen Schwartz
3eed636404 New translations en-us.json (German) 2025-12-24 16:12:11 -05:00
Owen Schwartz
a67f88381f New translations en-us.json (Czech) 2025-12-24 16:12:11 -05:00
Owen Schwartz
808fd856d1 New translations en-us.json (Bulgarian) 2025-12-24 16:12:11 -05:00
Owen Schwartz
5b9b532458 New translations en-us.json (Spanish) 2025-12-24 16:12:11 -05:00
miloschwartz
9fba9bd6b7 ui enhancements 2025-12-24 15:53:08 -05:00
Owen
c5ece144d0 Attempt to fix loginPageOrg undefined error 2025-12-24 12:25:11 -05:00
Owen
b64e2e11db Try to remove deadlocks on client updates 2025-12-24 12:20:22 -05:00
Owen
40eeb9b7cb Allow all in country in blueprints
Fixes #2163
2025-12-24 10:49:18 -05:00
Owen
8fa62a0908 Respect http status for url & maintenance mode
Fixes #2164
2025-12-24 10:47:01 -05:00
Owen
2bb94e24eb Merge branch 'main' into dev 2025-12-23 16:57:01 -05:00
Owen
ca89c5feca Reorder when the redirect gets in there 2025-12-23 16:02:52 -05:00
Owen
729c2adb3f Dont allow maintence page on remote nodes 2025-12-23 15:24:26 -05:00
miloschwartz
a21f49cb02 add sticky actions col to org idp table 2025-12-23 14:58:58 -05:00
miloschwartz
ef697c4864 adjustments to mobile header css closes #1930 2025-12-23 13:57:44 -05:00
miloschwartz
2652dea09a fade mobile footer 2025-12-23 13:41:11 -05:00
miloschwartz
efa9312fca fix server admin spacing on mobile sidebar 2025-12-23 13:37:48 -05:00
miloschwartz
074ee70025 add flag to disable product help banners 2025-12-23 13:33:24 -05:00
miloschwartz
77117e48e3 improved button loading animation 2025-12-23 12:51:38 -05:00
miloschwartz
da112d3417 add stripPortFromHost and reuse everywhere 2025-12-23 12:35:03 -05:00
Owen
ddaaf34dbd Merge branch 'dev' 2025-12-22 23:12:13 -05:00
miloschwartz
373e35324e Merge branch 'dev' of https://github.com/fosrl/pangolin into dev 2025-12-22 21:58:32 -05:00
miloschwartz
09b2f27749 show both redirect urls for org idp 2025-12-22 21:58:21 -05:00
Owen
7e9f18bf24 Update migration to allow all ports 2025-12-22 21:57:14 -05:00
Owen
ab3be26790 Working on remote nodes 2025-12-22 21:53:57 -05:00
Owen
5c67a1cb12 Format 2025-12-22 16:28:41 -05:00
Owen
e28ab19ed4 Add header 2025-12-22 16:28:19 -05:00
Owen
59f8334cfd Fix ee export of MaintenanceSchema 2025-12-22 16:27:54 -05:00
Milo Schwartz
718bec4bbc Merge pull request #2151 from fosrl/dev
1.14.0 ready
2025-12-22 13:16:30 -08:00
miloschwartz
2d731cb24b Merge branch 'main' into dev 2025-12-22 16:15:28 -05:00
miloschwartz
1905936950 parse request ip in exchange session 2025-12-22 15:48:24 -05:00
miloschwartz
c362bc673c add min version to product updates 2025-12-22 15:28:44 -05:00
miloschwartz
4da0a752ef make auto redirect to idp a select input 2025-12-22 15:03:57 -05:00
Owen
221ee6a1c2 Remove warning for limit 2025-12-22 14:07:49 -05:00
Owen
2e60ecec87 Add maintence options to blueprints 2025-12-22 14:00:50 -05:00
miloschwartz
71386d3b05 fix request ip port strip issue with badger >=1.3.0 2025-12-22 12:35:40 -05:00
Jacky Fong
89a7e2e4dc handle olm as well 2025-12-22 10:25:30 -05:00
Jacky Fong
27440700a5 fix: Don't treat newt release-candidate as a "update" in the site list 2025-12-22 10:25:30 -05:00
Owen
b5019cef12 Ignore the -arm64 and -amd64 tags 2025-12-21 21:21:24 -05:00
Owen
7e48cbe1aa Set file location 2025-12-21 21:16:57 -05:00
Owen
4b2c570e73 Fix bad rc check 2025-12-21 21:15:22 -05:00
112 changed files with 2769 additions and 1179 deletions

View File

@@ -99,7 +99,7 @@ jobs:
id: check-rc
run: |
TAG=${{ env.TAG }}
if [[ "$TAG" == *".rc."* ]]; then
if [[ "$TAG" == *"-rc."* ]]; then
echo "IS_RC=true" >> $GITHUB_ENV
else
echo "IS_RC=false" >> $GITHUB_ENV
@@ -171,7 +171,7 @@ jobs:
id: check-rc
run: |
TAG=${{ env.TAG }}
if [[ "$TAG" == *".rc."* ]]; then
if [[ "$TAG" == *"-rc."* ]]; then
echo "IS_RC=true" >> $GITHUB_ENV
else
echo "IS_RC=false" >> $GITHUB_ENV
@@ -219,7 +219,7 @@ jobs:
id: check-rc
run: |
TAG=${{ env.TAG }}
if [[ "$TAG" == *".rc."* ]]; then
if [[ "$TAG" == *"-rc."* ]]; then
echo "IS_RC=true" >> $GITHUB_ENV
else
echo "IS_RC=false" >> $GITHUB_ENV
@@ -322,13 +322,18 @@ jobs:
shell: bash
- name: Login to GHCR
env:
REGISTRY_AUTH_FILE: ${{ runner.temp }}/containers/auth.json
run: |
mkdir -p "$(dirname "$REGISTRY_AUTH_FILE")"
skopeo login ghcr.io -u "${{ github.actor }}" -p "${{ secrets.GITHUB_TOKEN }}"
shell: bash
- name: Copy tag from Docker Hub to GHCR
# Mirror the already-built image (all architectures) to GHCR so we can sign it
# Wait a bit for both architectures to be available in Docker Hub manifest
env:
REGISTRY_AUTH_FILE: ${{ runner.temp }}/containers/auth.json
run: |
set -euo pipefail
TAG=${{ env.TAG }}

View File

@@ -45,7 +45,7 @@ jobs:
run: |
set -euo pipefail
skopeo list-tags --retry-times 3 docker://"${SOURCE_IMAGE}" \
| jq -r '.Tags[]' | sort -u > src-tags.txt
| jq -r '.Tags[]' | grep -v -e '-arm64' -e '-amd64' | sort -u > src-tags.txt
echo "Found source tags: $(wc -l < src-tags.txt)"
head -n 20 src-tags.txt || true

96
:w
View File

@@ -1,96 +0,0 @@
import { db } from "@server/db/pg/driver";
import { sql } from "drizzle-orm";
import { __DIRNAME } from "@server/lib/consts";
const version = "1.14.0";
export default async function migration() {
console.log(`Running setup script ${version}...`);
try {
await db.execute(sql`BEGIN`);
await db.execute(sql`
CREATE TABLE "loginPageBranding" (
"loginPageBrandingId" serial PRIMARY KEY NOT NULL,
"logoUrl" text NOT NULL,
"logoWidth" integer NOT NULL,
"logoHeight" integer NOT NULL,
"primaryColor" text,
"resourceTitle" text NOT NULL,
"resourceSubtitle" text,
"orgTitle" text,
"orgSubtitle" text
);
`);
await db.execute(sql`
CREATE TABLE "loginPageBrandingOrg" (
"loginPageBrandingId" integer NOT NULL,
"orgId" varchar NOT NULL
);
`);
await db.execute(sql`
CREATE TABLE "resourceHeaderAuthExtendedCompatibility" (
"headerAuthExtendedCompatibilityId" serial PRIMARY KEY NOT NULL,
"resourceId" integer NOT NULL,
"extendedCompatibilityIsActivated" boolean DEFAULT false NOT NULL
);
`);
await db.execute(
sql`ALTER TABLE "resources" ADD COLUMN "maintenanceModeEnabled" boolean DEFAULT false NOT NULL;`
);
await db.execute(
sql`ALTER TABLE "resources" ADD COLUMN "maintenanceModeType" text DEFAULT 'forced';`
);
await db.execute(
sql`ALTER TABLE "resources" ADD COLUMN "maintenanceTitle" text;`
);
await db.execute(
sql`ALTER TABLE "resources" ADD COLUMN "maintenanceMessage" text;`
);
await db.execute(
sql`ALTER TABLE "resources" ADD COLUMN "maintenanceEstimatedTime" text;`
);
await db.execute(
sql`ALTER TABLE "siteResources" ADD COLUMN "tcpPortRangeString" varchar;`
);
await db.execute(
sql`ALTER TABLE "siteResources" ADD COLUMN "udpPortRangeString" varchar;`
);
await db.execute(
sql`ALTER TABLE "siteResources" ADD COLUMN "disableIcmp" boolean DEFAULT false NOT NULL;`
);
await db.execute(
sql`ALTER TABLE "loginPageBrandingOrg" ADD CONSTRAINT "loginPageBrandingOrg_loginPageBrandingId_loginPageBranding_loginPageBrandingId_fk" FOREIGN KEY ("loginPageBrandingId") REFERENCES "public"."loginPageBranding"("loginPageBrandingId") ON DELETE cascade ON UPDATE no action;`
);
await db.execute(
sql`ALTER TABLE "loginPageBrandingOrg" ADD CONSTRAINT "loginPageBrandingOrg_orgId_orgs_orgId_fk" FOREIGN KEY ("orgId") REFERENCES "public"."orgs"("orgId") ON DELETE cascade ON UPDATE no action;`
);
await db.execute(
sql`ALTER TABLE "resourceHeaderAuthExtendedCompatibility" ADD CONSTRAINT "resourceHeaderAuthExtendedCompatibility_resourceId_resources_resourceId_fk" FOREIGN KEY ("resourceId") REFERENCES "public"."resources"("resourceId") ON DELETE cascade ON UPDATE no action;`
);
await db.execute(sql`COMMIT`);
console.log("Migrated database");
} catch (e) {
await db.execute(sql`ROLLBACK`);
console.log("Unable to migrate database");
console.log(e);
throw e;
}
console.log(`${version} migration complete`);
}

View File

@@ -257,6 +257,8 @@
"accessRolesSearch": "Търсене на роли...",
"accessRolesAdd": "Добавете роля",
"accessRoleDelete": "Изтриване на роля",
"accessApprovalsManage": "Manage Approvals",
"accessApprovalsDescription": "Manage approval requests in the organization",
"description": "Описание",
"inviteTitle": "Отворени покани",
"inviteDescription": "Управлявайте покани за други потребители да се присъединят към организацията",
@@ -450,6 +452,18 @@
"selectDuration": "Изберете продължителност",
"selectResource": "Изберете Ресурс",
"filterByResource": "Филтрирай По Ресурс",
"selectApprovalState": "Select Approval State",
"filterByApprovalState": "Filter By Approval State",
"approvalListEmpty": "No approvals",
"approvalState": "Approval State",
"approve": "Approve",
"approved": "Approved",
"denied": "Denied",
"deniedApproval": "Denied Approval",
"all": "All",
"deny": "Deny",
"viewDetails": "View Details",
"requestingNewDeviceApproval": "requested a new device",
"resetFilters": "Нулиране на Филтрите",
"totalBlocked": "Заявки Блокирани От Pangolin",
"totalRequests": "Общо Заявки",
@@ -729,16 +743,28 @@
"countries": "Държави",
"accessRoleCreate": "Създайте роля",
"accessRoleCreateDescription": "Създайте нова роля за групиране на потребители и управление на техните разрешения.",
"accessRoleEdit": "Edit Role",
"accessRoleEditDescription": "Edit role information.",
"accessRoleCreateSubmit": "Създайте роля",
"accessRoleCreated": "Ролята е създадена",
"accessRoleCreatedDescription": "Ролята беше успешно създадена.",
"accessRoleErrorCreate": "Неуспешно създаване на роля",
"accessRoleErrorCreateDescription": "Възникна грешка при създаването на ролята.",
"accessRoleUpdateSubmit": "Update Role",
"accessRoleUpdated": "Role updated",
"accessRoleUpdatedDescription": "The role has been successfully updated.",
"accessApprovalUpdated": "Approval processed",
"accessApprovalApprovedDescription": "Set Approval Request decision to approved.",
"accessApprovalDeniedDescription": "Set Approval Request decision to denied.",
"accessRoleErrorUpdate": "Failed to update role",
"accessRoleErrorUpdateDescription": "An error occurred while updating the role.",
"accessApprovalErrorUpdate": "Failed to process approval",
"accessApprovalErrorUpdateDescription": "An error occurred while processing the approval.",
"accessRoleErrorNewRequired": "Нова роля е необходима",
"accessRoleErrorRemove": "Неуспешно премахване на роля",
"accessRoleErrorRemoveDescription": "Възникна грешка при премахването на роля.",
"accessRoleName": "Име на роля",
"accessRoleQuestionRemove": "Ще изтриете ролята {name}. Не можете да отмените това действие.",
"accessRoleQuestionRemove": "You're about to delete the `{name}` role. You cannot undo this action.",
"accessRoleRemove": "Премахни роля",
"accessRoleRemoveDescription": "Премахни роля от организацията",
"accessRoleRemoveSubmit": "Премахни роля",
@@ -850,6 +876,7 @@
"orgPolicyConfig": "Конфигуриране на достъп за организация",
"idpUpdatedDescription": "Идентификационният доставчик беше актуализиран успешно",
"redirectUrl": "URL за пренасочване",
"orgIdpRedirectUrls": "URL адреси за пренасочване",
"redirectUrlAbout": "За URL за пренасочване",
"redirectUrlAboutDescription": "Това е URL адресът, към който потребителите ще бъдат пренасочени след удостоверяване. Трябва да конфигурирате този URL адрес в настройките на доставчика на идентичност.",
"pangolinAuth": "Authent - Pangolin",
@@ -873,7 +900,7 @@
"inviteAlready": "Изглежда, че сте били поканени!",
"inviteAlreadyDescription": "За да приемете поканата, трябва да влезете или да създадете акаунт.",
"signupQuestion": "Вече имате акаунт?",
"login": "Влизане",
"login": "Log In",
"resourceNotFound": "Ресурсът не е намерен",
"resourceNotFoundDescription": "Ресурсът, който се опитвате да достъпите, не съществува.",
"pincodeRequirementsLength": "ПИН трябва да бъде точно 6 цифри",
@@ -953,13 +980,13 @@
"passwordExpiryDescription": "Тази организация изисква да сменяте паролата си на всеки {maxDays} дни.",
"changePasswordNow": "Сменете паролата сега",
"pincodeAuth": "Код на удостоверителя",
"pincodeSubmit2": "Изпрати код",
"pincodeSubmit2": "Submit code",
"passwordResetSubmit": "Заявка за нулиране",
"passwordResetAlreadyHaveCode": "Въведете код.",
"passwordResetSmtpRequired": "Моля, свържете се с вашия администратор",
"passwordResetSmtpRequiredDescription": "Кодът за нулиране на парола е задължителен за нулиране на паролата ви. Моля, свържете се с вашия администратор за помощ.",
"passwordBack": "Назад към Парола",
"loginBack": "Връщане към вход",
"loginBack": "Go back to main login page",
"signup": "Регистрация",
"loginStart": "Влезте, за да започнете",
"idpOidcTokenValidating": "Валидиране на OIDC токен",
@@ -1117,6 +1144,10 @@
"actionUpdateIdpOrg": "Актуализиране на IdP организация",
"actionCreateClient": "Създаване на клиент",
"actionDeleteClient": "Изтриване на клиент",
"actionArchiveClient": "Archive Client",
"actionUnarchiveClient": "Unarchive Client",
"actionBlockClient": "Block Client",
"actionUnblockClient": "Unblock Client",
"actionUpdateClient": "Актуализиране на клиент",
"actionListClients": "Списък с клиенти",
"actionGetClient": "Получаване на клиент",
@@ -1133,14 +1164,14 @@
"searchProgress": "Търсене...",
"create": "Създаване",
"orgs": "Организации",
"loginError": "Възникна грешка при влизане",
"loginRequiredForDevice": "Необходим е вход за удостоверяване на вашето устройство.",
"loginError": "An unexpected error occurred. Please try again.",
"loginRequiredForDevice": "Login is required for your device.",
"passwordForgot": "Забравена парола?",
"otpAuth": "Двуфакторно удостоверяване",
"otpAuthDescription": "Въведете кода от приложението за удостоверяване или един от вашите резервни кодове за еднократна употреба.",
"otpAuthSubmit": "Изпрати код",
"idpContinue": "Или продължете със",
"otpAuthBack": "Назад към Вход",
"otpAuthBack": "Back to Password",
"navbar": "Навигационно меню",
"navbarDescription": "Главно навигационно меню за приложението",
"navbarDocsLink": "Документация",
@@ -1188,6 +1219,7 @@
"sidebarOverview": "Общ преглед",
"sidebarHome": "Начало",
"sidebarSites": "Сайтове",
"sidebarApprovals": "Approval Requests",
"sidebarResources": "Ресурси",
"sidebarProxyResources": "Публично",
"sidebarClientResources": "Частно",
@@ -1204,7 +1236,7 @@
"sidebarIdentityProviders": "Идентификационни доставчици",
"sidebarLicense": "Лиценз",
"sidebarClients": "Клиенти",
"sidebarUserDevices": "Потребители",
"sidebarUserDevices": "User Devices",
"sidebarMachineClients": "Машини",
"sidebarDomains": "Домейни",
"sidebarGeneral": "Управление.",
@@ -1303,6 +1335,7 @@
"refreshError": "Неуспешно обновяване на данни",
"verified": "Потвърдено",
"pending": "Чакащо",
"pendingApproval": "Pending Approval",
"sidebarBilling": "Фактуриране",
"billing": "Фактуриране",
"orgBillingDescription": "Управлявайте информацията за плащане и абонаментите",
@@ -1419,7 +1452,7 @@
"securityKeyRemoveSuccess": "Ключът за защита е премахнат успешно",
"securityKeyRemoveError": "Неуспешно премахване на ключ за защита",
"securityKeyLoadError": "Неуспешно зареждане на ключове за защита",
"securityKeyLogin": "Продължете с ключа за сигурност",
"securityKeyLogin": "Use Security Key",
"securityKeyAuthError": "Неуспешно удостоверяване с ключ за сигурност",
"securityKeyRecommendation": "Регистрирайте резервен ключ за безопасност на друго устройство, за да сте сигурни, че винаги ще имате достъп до профила си",
"registering": "Регистрация...",
@@ -1479,7 +1512,7 @@
"IAgreeToThe": "Съгласен съм с",
"termsOfService": "условията за ползване",
"and": "и",
"privacyPolicy": "политиката за поверителност"
"privacyPolicy": "политика за поверителност."
},
"signUpMarketing": {
"keepMeInTheLoop": "Дръж ме в течение с новини, актуализации и нови функции чрез имейл."
@@ -1546,6 +1579,8 @@
"IntervalSeconds": "Интервал за здраве",
"timeoutSeconds": "Време за изчакване (сек)",
"timeIsInSeconds": "Времето е в секунди",
"requireDeviceApproval": "Require Device Approvals",
"requireDeviceApprovalDescription": "Users with this role need their devices approved by an admin before they can access resources",
"retryAttempts": "Опити за повторно",
"expectedResponseCodes": "Очаквани кодове за отговор",
"expectedResponseCodesDescription": "HTTP статус код, указващ здравословно състояние. Ако бъде оставено празно, между 200-300 се счита за здравословно.",
@@ -1875,7 +1910,7 @@
"orgAuthChooseIdpDescription": "Изберете своя доставчик на идентичност, за да продължите",
"orgAuthNoIdpConfigured": "Тази организация няма конфигурирани доставчици на идентичност. Можете да влезете с вашата Pangolin идентичност.",
"orgAuthSignInWithPangolin": "Впишете се с Pangolin",
"orgAuthSignInToOrg": "Влезте в организация.",
"orgAuthSignInToOrg": "Sign in to an organization",
"orgAuthSelectOrgTitle": "Вход в организация.",
"orgAuthSelectOrgDescription": "Въведете идентификатора на вашата организация, за да продължите.",
"orgAuthOrgIdPlaceholder": "вашата-организация",
@@ -2231,6 +2266,8 @@
"deviceCodeInvalidFormat": "Кодът трябва да бъде 9 символа (напр. A1AJ-N5JD)",
"deviceCodeInvalidOrExpired": "Невалиден или изтекъл код",
"deviceCodeVerifyFailed": "Неуспешна проверка на кода на устройството",
"deviceCodeValidating": "Validating device code...",
"deviceCodeVerifying": "Verifying device authorization...",
"signedInAs": "Вписан като",
"deviceCodeEnterPrompt": "Въведете кода, показан на устройството",
"continue": "Продължете",
@@ -2243,7 +2280,7 @@
"deviceOrganizationsAccess": "Достъп до всички организации, до които има достъп акаунтът ви",
"deviceAuthorize": "Разрешете {applicationName}",
"deviceConnected": "Устройството е свързано!",
"deviceAuthorizedMessage": "Устройството е разрешено да има достъп до вашия акаунт.",
"deviceAuthorizedMessage": "Device is authorized to access your account. Please return to the client application.",
"pangolinCloud": "Pangolin Cloud",
"viewDevices": "Преглед на устройствата",
"viewDevicesDescription": "Управлявайте свързаните си устройства",
@@ -2305,6 +2342,7 @@
"identifier": "Идентификатор",
"deviceLoginUseDifferentAccount": "Не сте вие? Използвайте друг акаунт.",
"deviceLoginDeviceRequestingAccessToAccount": "Устройство запитващо достъп до този акаунт.",
"loginSelectAuthenticationMethod": "Select an authentication method to continue.",
"noData": "Няма Данни",
"machineClients": "Машинни клиенти",
"install": "Инсталирай",
@@ -2349,6 +2387,7 @@
"enterConfirmation": "Въведете потвърждение.",
"blueprintViewDetails": "Подробности.",
"defaultIdentityProvider": "По подразбиране доставчик на идентичност.",
"defaultIdentityProviderDescription": "Когато е избран основен доставчик на идентичност, потребителят ще бъде автоматично пренасочен към доставчика за удостоверяване.",
"editInternalResourceDialogNetworkSettings": "Мрежови настройки.",
"editInternalResourceDialogAccessPolicy": "Политика за достъп.",
"editInternalResourceDialogAddRoles": "Добавяне на роли.",
@@ -2392,5 +2431,56 @@
"maintenanceScreenTitle": "Услугата временно недостъпна.",
"maintenanceScreenMessage": "В момента срещаме технически затруднения. Моля, проверете отново скоро.",
"maintenanceScreenEstimatedCompletion": "Прогнозно завършване:",
"createInternalResourceDialogDestinationRequired": "Дестинацията е задължителна."
"createInternalResourceDialogDestinationRequired": "Дестинацията е задължителна.",
"available": "Available",
"archived": "Archived",
"noArchivedDevices": "No archived devices found",
"deviceArchived": "Device archived",
"deviceArchivedDescription": "The device has been successfully archived.",
"errorArchivingDevice": "Error archiving device",
"failedToArchiveDevice": "Failed to archive device",
"deviceQuestionArchive": "Are you sure you want to archive this device?",
"deviceMessageArchive": "The device will be archived and removed from your active devices list.",
"deviceArchiveConfirm": "Archive Device",
"archiveDevice": "Archive Device",
"archive": "Archive",
"deviceUnarchived": "Device unarchived",
"deviceUnarchivedDescription": "The device has been successfully unarchived.",
"errorUnarchivingDevice": "Error unarchiving device",
"failedToUnarchiveDevice": "Failed to unarchive device",
"unarchive": "Unarchive",
"archiveClient": "Archive Client",
"archiveClientQuestion": "Are you sure you want to archive this client?",
"archiveClientMessage": "The client will be archived and removed from your active clients list.",
"archiveClientConfirm": "Archive Client",
"blockClient": "Block Client",
"blockClientQuestion": "Are you sure you want to block this client?",
"blockClientMessage": "The device will be forced to disconnect if currently connected. You can unblock the device later.",
"blockClientConfirm": "Block Client",
"active": "Active",
"usernameOrEmail": "Username or Email",
"selectYourOrganization": "Select your organization",
"signInTo": "Log in in to",
"signInWithPassword": "Continue with Password",
"noAuthMethodsAvailable": "No authentication methods available for this organization.",
"enterPassword": "Enter your password",
"enterMfaCode": "Enter the code from your authenticator app",
"securityKeyRequired": "Please use your security key to sign in.",
"needToUseAnotherAccount": "Need to use a different account?",
"loginLegalDisclaimer": "By clicking the buttons below, you acknowledge you have read, understand, and agree to the <termsOfService>Terms of Service</termsOfService> and <privacyPolicy>Privacy Policy</privacyPolicy>.",
"termsOfService": "Terms of Service",
"privacyPolicy": "Privacy Policy",
"userNotFoundWithUsername": "No user found with that username.",
"verify": "Verify",
"signIn": "Sign In",
"forgotPassword": "Forgot password?",
"orgSignInTip": "If you've logged in before, you can enter your username or email above to authenticate with your organization's identity provider instead. It's easier!",
"continueAnyway": "Continue anyway",
"dontShowAgain": "Don't show again",
"orgSignInNotice": "Did you know?",
"signupOrgNotice": "Trying to sign in?",
"signupOrgTip": "Are you trying to sign in through your organization's identity provider?",
"signupOrgLink": "Sign in or sign up with your organization instead",
"verifyEmailLogInWithDifferentAccount": "Use a Different Account",
"logIn": "Log In"
}

View File

@@ -257,6 +257,8 @@
"accessRolesSearch": "Hledat role...",
"accessRolesAdd": "Přidat roli",
"accessRoleDelete": "Odstranit roli",
"accessApprovalsManage": "Manage Approvals",
"accessApprovalsDescription": "Manage approval requests in the organization",
"description": "L 343, 22.12.2009, s. 1).",
"inviteTitle": "Otevřít pozvánky",
"inviteDescription": "Spravovat pozvánky pro ostatní uživatele do organizace",
@@ -450,6 +452,18 @@
"selectDuration": "Vyberte dobu trvání",
"selectResource": "Vybrat dokument",
"filterByResource": "Filtrovat podle zdroje",
"selectApprovalState": "Select Approval State",
"filterByApprovalState": "Filter By Approval State",
"approvalListEmpty": "No approvals",
"approvalState": "Approval State",
"approve": "Approve",
"approved": "Approved",
"denied": "Denied",
"deniedApproval": "Denied Approval",
"all": "All",
"deny": "Deny",
"viewDetails": "View Details",
"requestingNewDeviceApproval": "requested a new device",
"resetFilters": "Resetovat filtry",
"totalBlocked": "Požadavky blokovány Pangolinem",
"totalRequests": "Celkem požadavků",
@@ -729,16 +743,28 @@
"countries": "Země",
"accessRoleCreate": "Vytvořit roli",
"accessRoleCreateDescription": "Vytvořte novou roli pro seskupení uživatelů a spravujte jejich oprávnění.",
"accessRoleEdit": "Edit Role",
"accessRoleEditDescription": "Edit role information.",
"accessRoleCreateSubmit": "Vytvořit roli",
"accessRoleCreated": "Role vytvořena",
"accessRoleCreatedDescription": "Role byla úspěšně vytvořena.",
"accessRoleErrorCreate": "Nepodařilo se vytvořit roli",
"accessRoleErrorCreateDescription": "Došlo k chybě při vytváření role.",
"accessRoleUpdateSubmit": "Update Role",
"accessRoleUpdated": "Role updated",
"accessRoleUpdatedDescription": "The role has been successfully updated.",
"accessApprovalUpdated": "Approval processed",
"accessApprovalApprovedDescription": "Set Approval Request decision to approved.",
"accessApprovalDeniedDescription": "Set Approval Request decision to denied.",
"accessRoleErrorUpdate": "Failed to update role",
"accessRoleErrorUpdateDescription": "An error occurred while updating the role.",
"accessApprovalErrorUpdate": "Failed to process approval",
"accessApprovalErrorUpdateDescription": "An error occurred while processing the approval.",
"accessRoleErrorNewRequired": "Je vyžadována nová role",
"accessRoleErrorRemove": "Nepodařilo se odstranit roli",
"accessRoleErrorRemoveDescription": "Došlo k chybě při odstraňování role.",
"accessRoleName": "Název role",
"accessRoleQuestionRemove": "Chystáte se odstranit {name} roli. Tuto akci nelze vrátit zpět.",
"accessRoleQuestionRemove": "You're about to delete the `{name}` role. You cannot undo this action.",
"accessRoleRemove": "Odstranit roli",
"accessRoleRemoveDescription": "Odebrat roli z organizace",
"accessRoleRemoveSubmit": "Odstranit roli",
@@ -850,6 +876,7 @@
"orgPolicyConfig": "Konfigurace přístupu pro organizaci",
"idpUpdatedDescription": "Poskytovatel identity byl úspěšně aktualizován",
"redirectUrl": "Přesměrovat URL",
"orgIdpRedirectUrls": "Přesměrovat URL",
"redirectUrlAbout": "O přesměrování URL",
"redirectUrlAboutDescription": "Toto je URL, na kterou budou uživatelé po ověření přesměrováni. Tuto URL je třeba nastavit v nastavení poskytovatele identity.",
"pangolinAuth": "Auth - Pangolin",
@@ -873,7 +900,7 @@
"inviteAlready": "Vypadá to, že jste byli pozváni!",
"inviteAlreadyDescription": "Chcete-li přijmout pozvánku, musíte se přihlásit nebo vytvořit účet.",
"signupQuestion": "Již máte účet?",
"login": "Přihlásit se",
"login": "Log In",
"resourceNotFound": "Zdroj nebyl nalezen",
"resourceNotFoundDescription": "Dokument, ke kterému se snažíte přistupovat, neexistuje.",
"pincodeRequirementsLength": "PIN musí být přesně 6 číslic",
@@ -953,13 +980,13 @@
"passwordExpiryDescription": "Tato organizace vyžaduje změnu hesla každých {maxDays} dní.",
"changePasswordNow": "Změnit heslo",
"pincodeAuth": "Ověřovací kód",
"pincodeSubmit2": "Odeslat kód",
"pincodeSubmit2": "Submit code",
"passwordResetSubmit": "Žádost o obnovení",
"passwordResetAlreadyHaveCode": "Zadejte kód",
"passwordResetSmtpRequired": "Obraťte se na správce",
"passwordResetSmtpRequiredDescription": "Pro obnovení hesla je vyžadován kód pro obnovení hesla. Kontaktujte prosím svého administrátora.",
"passwordBack": "Zpět na heslo",
"loginBack": "Přejít zpět na přihlášení",
"loginBack": "Go back to main login page",
"signup": "Zaregistrovat se",
"loginStart": "Přihlaste se a začněte",
"idpOidcTokenValidating": "Ověřování OIDC tokenu",
@@ -1117,6 +1144,10 @@
"actionUpdateIdpOrg": "Aktualizovat IDP Org",
"actionCreateClient": "Vytvořit klienta",
"actionDeleteClient": "Odstranit klienta",
"actionArchiveClient": "Archive Client",
"actionUnarchiveClient": "Unarchive Client",
"actionBlockClient": "Block Client",
"actionUnblockClient": "Unblock Client",
"actionUpdateClient": "Aktualizovat klienta",
"actionListClients": "Seznam klientů",
"actionGetClient": "Získat klienta",
@@ -1133,14 +1164,14 @@
"searchProgress": "Hledat...",
"create": "Vytvořit",
"orgs": "Organizace",
"loginError": "Při přihlášení došlo k chybě",
"loginRequiredForDevice": "Pro ověření vašeho zařízení je nutné se přihlásit.",
"loginError": "An unexpected error occurred. Please try again.",
"loginRequiredForDevice": "Login is required for your device.",
"passwordForgot": "Zapomněli jste heslo?",
"otpAuth": "Dvoufaktorové ověření",
"otpAuthDescription": "Zadejte kód z vaší autentizační aplikace nebo jeden z vlastních záložních kódů.",
"otpAuthSubmit": "Odeslat kód",
"idpContinue": "Nebo pokračovat s",
"otpAuthBack": "Zpět na přihlášení",
"otpAuthBack": "Back to Password",
"navbar": "Navigation Menu",
"navbarDescription": "Hlavní navigační menu aplikace",
"navbarDocsLink": "Dokumentace",
@@ -1188,6 +1219,7 @@
"sidebarOverview": "Přehled",
"sidebarHome": "Domů",
"sidebarSites": "Stránky",
"sidebarApprovals": "Approval Requests",
"sidebarResources": "Zdroje",
"sidebarProxyResources": "Veřejnost",
"sidebarClientResources": "Soukromé",
@@ -1204,7 +1236,7 @@
"sidebarIdentityProviders": "Poskytovatelé identity",
"sidebarLicense": "Licence",
"sidebarClients": "Klienti",
"sidebarUserDevices": "Uživatelé",
"sidebarUserDevices": "User Devices",
"sidebarMachineClients": "Stroje a přístroje",
"sidebarDomains": "Domény",
"sidebarGeneral": "Spravovat",
@@ -1303,6 +1335,7 @@
"refreshError": "Obnovení dat se nezdařilo",
"verified": "Ověřeno",
"pending": "Nevyřízeno",
"pendingApproval": "Pending Approval",
"sidebarBilling": "Fakturace",
"billing": "Fakturace",
"orgBillingDescription": "Spravovat fakturační informace a předplatné",
@@ -1419,7 +1452,7 @@
"securityKeyRemoveSuccess": "Bezpečnostní klíč byl úspěšně odstraněn",
"securityKeyRemoveError": "Odstranění bezpečnostního klíče se nezdařilo",
"securityKeyLoadError": "Nepodařilo se načíst bezpečnostní klíče",
"securityKeyLogin": "Pokračovat s bezpečnostním klíčem",
"securityKeyLogin": "Use Security Key",
"securityKeyAuthError": "Ověření bezpečnostním klíčem se nezdařilo",
"securityKeyRecommendation": "Registrujte záložní bezpečnostní klíč na jiném zařízení, abyste zajistili, že budete mít vždy přístup ke svému účtu.",
"registering": "Registrace...",
@@ -1479,7 +1512,7 @@
"IAgreeToThe": "Souhlasím s",
"termsOfService": "podmínky služby",
"and": "a",
"privacyPolicy": "zásady ochrany osobních údajů"
"privacyPolicy": "zásady ochrany osobních údajů."
},
"signUpMarketing": {
"keepMeInTheLoop": "Udržujte mě ve smyčce s novinkami, aktualizacemi a novými funkcemi e-mailem."
@@ -1546,6 +1579,8 @@
"IntervalSeconds": "Interval zdraví",
"timeoutSeconds": "Časový limit (sek)",
"timeIsInSeconds": "Čas je v sekundách",
"requireDeviceApproval": "Require Device Approvals",
"requireDeviceApprovalDescription": "Users with this role need their devices approved by an admin before they can access resources",
"retryAttempts": "Opakovat pokusy",
"expectedResponseCodes": "Očekávané kódy odezvy",
"expectedResponseCodesDescription": "HTTP kód stavu, který označuje zdravý stav. Ponecháte-li prázdné, 200-300 je považováno za zdravé.",
@@ -1875,7 +1910,7 @@
"orgAuthChooseIdpDescription": "Chcete-li pokračovat, vyberte svého poskytovatele identity",
"orgAuthNoIdpConfigured": "Tato organizace nemá nakonfigurovány žádné poskytovatele identity. Místo toho se můžete přihlásit s vaší Pangolinovou identitou.",
"orgAuthSignInWithPangolin": "Přihlásit se pomocí Pangolinu",
"orgAuthSignInToOrg": "Přihlaste se do organizace",
"orgAuthSignInToOrg": "Sign in to an organization",
"orgAuthSelectOrgTitle": "Přihlášení do organizace",
"orgAuthSelectOrgDescription": "Zadejte ID vaší organizace pro pokračování",
"orgAuthOrgIdPlaceholder": "vaše-organizace",
@@ -2231,6 +2266,8 @@
"deviceCodeInvalidFormat": "Kód musí být 9 znaků (např. A1AJ-N5JD)",
"deviceCodeInvalidOrExpired": "Neplatný nebo prošlý kód",
"deviceCodeVerifyFailed": "Ověření kódu zařízení se nezdařilo",
"deviceCodeValidating": "Validating device code...",
"deviceCodeVerifying": "Verifying device authorization...",
"signedInAs": "Přihlášen jako",
"deviceCodeEnterPrompt": "Zadejte kód zobrazený na zařízení",
"continue": "Pokračovat",
@@ -2243,7 +2280,7 @@
"deviceOrganizationsAccess": "Přístup ke všem organizacím má přístup k vašemu účtu",
"deviceAuthorize": "Autorizovat {applicationName}",
"deviceConnected": "Zařízení připojeno!",
"deviceAuthorizedMessage": "Zařízení má oprávnění k přístupu k vašemu účtu.",
"deviceAuthorizedMessage": "Device is authorized to access your account. Please return to the client application.",
"pangolinCloud": "Pangolin Cloud",
"viewDevices": "Zobrazit zařízení",
"viewDevicesDescription": "Spravovat připojená zařízení",
@@ -2305,6 +2342,7 @@
"identifier": "Identifier",
"deviceLoginUseDifferentAccount": "Nejste vy? Použijte jiný účet.",
"deviceLoginDeviceRequestingAccessToAccount": "Zařízení žádá o přístup k tomuto účtu.",
"loginSelectAuthenticationMethod": "Select an authentication method to continue.",
"noData": "Žádná data",
"machineClients": "Strojoví klienti",
"install": "Instalovat",
@@ -2349,6 +2387,7 @@
"enterConfirmation": "Zadejte potvrzení",
"blueprintViewDetails": "Detaily",
"defaultIdentityProvider": "Výchozí poskytovatel identity",
"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ě",
"editInternalResourceDialogAccessPolicy": "Přístupová politika",
"editInternalResourceDialogAddRoles": "Přidat role",
@@ -2392,5 +2431,56 @@
"maintenanceScreenTitle": "Služba dočasně nedostupná",
"maintenanceScreenMessage": "Momentálně máme technické potíže. Zkontrolujte později.",
"maintenanceScreenEstimatedCompletion": "Odhadované dokončení:",
"createInternalResourceDialogDestinationRequired": "Cíl je povinný"
"createInternalResourceDialogDestinationRequired": "Cíl je povinný",
"available": "Available",
"archived": "Archived",
"noArchivedDevices": "No archived devices found",
"deviceArchived": "Device archived",
"deviceArchivedDescription": "The device has been successfully archived.",
"errorArchivingDevice": "Error archiving device",
"failedToArchiveDevice": "Failed to archive device",
"deviceQuestionArchive": "Are you sure you want to archive this device?",
"deviceMessageArchive": "The device will be archived and removed from your active devices list.",
"deviceArchiveConfirm": "Archive Device",
"archiveDevice": "Archive Device",
"archive": "Archive",
"deviceUnarchived": "Device unarchived",
"deviceUnarchivedDescription": "The device has been successfully unarchived.",
"errorUnarchivingDevice": "Error unarchiving device",
"failedToUnarchiveDevice": "Failed to unarchive device",
"unarchive": "Unarchive",
"archiveClient": "Archive Client",
"archiveClientQuestion": "Are you sure you want to archive this client?",
"archiveClientMessage": "The client will be archived and removed from your active clients list.",
"archiveClientConfirm": "Archive Client",
"blockClient": "Block Client",
"blockClientQuestion": "Are you sure you want to block this client?",
"blockClientMessage": "The device will be forced to disconnect if currently connected. You can unblock the device later.",
"blockClientConfirm": "Block Client",
"active": "Active",
"usernameOrEmail": "Username or Email",
"selectYourOrganization": "Select your organization",
"signInTo": "Log in in to",
"signInWithPassword": "Continue with Password",
"noAuthMethodsAvailable": "No authentication methods available for this organization.",
"enterPassword": "Enter your password",
"enterMfaCode": "Enter the code from your authenticator app",
"securityKeyRequired": "Please use your security key to sign in.",
"needToUseAnotherAccount": "Need to use a different account?",
"loginLegalDisclaimer": "By clicking the buttons below, you acknowledge you have read, understand, and agree to the <termsOfService>Terms of Service</termsOfService> and <privacyPolicy>Privacy Policy</privacyPolicy>.",
"termsOfService": "Terms of Service",
"privacyPolicy": "Privacy Policy",
"userNotFoundWithUsername": "No user found with that username.",
"verify": "Verify",
"signIn": "Sign In",
"forgotPassword": "Forgot password?",
"orgSignInTip": "If you've logged in before, you can enter your username or email above to authenticate with your organization's identity provider instead. It's easier!",
"continueAnyway": "Continue anyway",
"dontShowAgain": "Don't show again",
"orgSignInNotice": "Did you know?",
"signupOrgNotice": "Trying to sign in?",
"signupOrgTip": "Are you trying to sign in through your organization's identity provider?",
"signupOrgLink": "Sign in or sign up with your organization instead",
"verifyEmailLogInWithDifferentAccount": "Use a Different Account",
"logIn": "Log In"
}

View File

@@ -257,6 +257,8 @@
"accessRolesSearch": "Rollen suchen...",
"accessRolesAdd": "Rolle hinzufügen",
"accessRoleDelete": "Rolle löschen",
"accessApprovalsManage": "Manage Approvals",
"accessApprovalsDescription": "Manage approval requests in the organization",
"description": "Beschreibung",
"inviteTitle": "Einladungen öffnen",
"inviteDescription": "Einladungen für andere Benutzer verwalten, der Organisation beizutreten",
@@ -450,6 +452,18 @@
"selectDuration": "Dauer auswählen",
"selectResource": "Ressource auswählen",
"filterByResource": "Nach Ressource filtern",
"selectApprovalState": "Select Approval State",
"filterByApprovalState": "Filter By Approval State",
"approvalListEmpty": "No approvals",
"approvalState": "Approval State",
"approve": "Approve",
"approved": "Approved",
"denied": "Denied",
"deniedApproval": "Denied Approval",
"all": "All",
"deny": "Deny",
"viewDetails": "View Details",
"requestingNewDeviceApproval": "requested a new device",
"resetFilters": "Filter zurücksetzen",
"totalBlocked": "Anfragen blockiert von Pangolin",
"totalRequests": "Gesamte Anfragen",
@@ -729,16 +743,28 @@
"countries": "Länder",
"accessRoleCreate": "Rolle erstellen",
"accessRoleCreateDescription": "Erstellen Sie eine neue Rolle, um Benutzer zu gruppieren und ihre Berechtigungen zu verwalten.",
"accessRoleEdit": "Edit Role",
"accessRoleEditDescription": "Edit role information.",
"accessRoleCreateSubmit": "Rolle erstellen",
"accessRoleCreated": "Rolle erstellt",
"accessRoleCreatedDescription": "Die Rolle wurde erfolgreich erstellt.",
"accessRoleErrorCreate": "Fehler beim Erstellen der Rolle",
"accessRoleErrorCreateDescription": "Beim Erstellen der Rolle ist ein Fehler aufgetreten.",
"accessRoleUpdateSubmit": "Update Role",
"accessRoleUpdated": "Role updated",
"accessRoleUpdatedDescription": "The role has been successfully updated.",
"accessApprovalUpdated": "Approval processed",
"accessApprovalApprovedDescription": "Set Approval Request decision to approved.",
"accessApprovalDeniedDescription": "Set Approval Request decision to denied.",
"accessRoleErrorUpdate": "Failed to update role",
"accessRoleErrorUpdateDescription": "An error occurred while updating the role.",
"accessApprovalErrorUpdate": "Failed to process approval",
"accessApprovalErrorUpdateDescription": "An error occurred while processing the approval.",
"accessRoleErrorNewRequired": "Neue Rolle ist erforderlich",
"accessRoleErrorRemove": "Fehler beim Entfernen der Rolle",
"accessRoleErrorRemoveDescription": "Beim Entfernen der Rolle ist ein Fehler aufgetreten.",
"accessRoleName": "Rollenname",
"accessRoleQuestionRemove": "Sie sind dabei, die Rolle {name} zu löschen. Diese Aktion kann nicht rückgängig gemacht werden.",
"accessRoleQuestionRemove": "You're about to delete the `{name}` role. You cannot undo this action.",
"accessRoleRemove": "Rolle entfernen",
"accessRoleRemoveDescription": "Eine Rolle aus der Organisation entfernen",
"accessRoleRemoveSubmit": "Rolle entfernen",
@@ -850,6 +876,7 @@
"orgPolicyConfig": "Zugriff für eine Organisation konfigurieren",
"idpUpdatedDescription": "Identitätsanbieter erfolgreich aktualisiert",
"redirectUrl": "Weiterleitungs-URL",
"orgIdpRedirectUrls": "Umleitungs-URLs",
"redirectUrlAbout": "Über die Weiterleitungs-URL",
"redirectUrlAboutDescription": "Dies ist die URL, zu der Benutzer nach der Authentifizierung umgeleitet werden. Sie müssen diese URL in den Einstellungen des Identity Providers konfigurieren.",
"pangolinAuth": "Authentifizierung - Pangolin",
@@ -873,7 +900,7 @@
"inviteAlready": "Sieht aus, als wären Sie eingeladen worden!",
"inviteAlreadyDescription": "Um die Einladung anzunehmen, müssen Sie sich einloggen oder ein Konto erstellen.",
"signupQuestion": "Haben Sie bereits ein Konto?",
"login": "Anmelden",
"login": "Log In",
"resourceNotFound": "Ressource nicht gefunden",
"resourceNotFoundDescription": "Die Ressource, auf die Sie zugreifen möchten, existiert nicht.",
"pincodeRequirementsLength": "PIN muss genau 6 Ziffern lang sein",
@@ -953,13 +980,13 @@
"passwordExpiryDescription": "Diese Organisation erfordert, dass Sie Ihr Passwort alle {maxDays} Tage ändern.",
"changePasswordNow": "Passwort jetzt ändern",
"pincodeAuth": "Authentifizierungscode",
"pincodeSubmit2": "Code absenden",
"pincodeSubmit2": "Submit code",
"passwordResetSubmit": "Zurücksetzung anfordern",
"passwordResetAlreadyHaveCode": "Code eingeben",
"passwordResetSmtpRequired": "Bitte kontaktieren Sie Ihren Administrator",
"passwordResetSmtpRequiredDescription": "Zum Zurücksetzen Ihres Passworts ist ein Passwort erforderlich. Bitte wenden Sie sich an Ihren Administrator.",
"passwordBack": "Zurück zum Passwort",
"loginBack": "Zurück zur Anmeldung",
"loginBack": "Go back to main login page",
"signup": "Registrieren",
"loginStart": "Melden Sie sich an, um zu beginnen",
"idpOidcTokenValidating": "OIDC-Token wird validiert",
@@ -1117,6 +1144,10 @@
"actionUpdateIdpOrg": "IDP-Organisation aktualisieren",
"actionCreateClient": "Client erstellen",
"actionDeleteClient": "Client löschen",
"actionArchiveClient": "Archive Client",
"actionUnarchiveClient": "Unarchive Client",
"actionBlockClient": "Block Client",
"actionUnblockClient": "Unblock Client",
"actionUpdateClient": "Client aktualisieren",
"actionListClients": "Clients auflisten",
"actionGetClient": "Clients abrufen",
@@ -1133,14 +1164,14 @@
"searchProgress": "Suche...",
"create": "Erstellen",
"orgs": "Organisationen",
"loginError": "Beim Anmelden ist ein Fehler aufgetreten",
"loginRequiredForDevice": "Zur Authentifizierung Ihres Geräts ist eine Anmeldung erforderlich",
"loginError": "An unexpected error occurred. Please try again.",
"loginRequiredForDevice": "Anmeldung ist für Ihr Gerät erforderlich.",
"passwordForgot": "Passwort vergessen?",
"otpAuth": "Zwei-Faktor-Authentifizierung",
"otpAuthDescription": "Geben Sie den Code aus Ihrer Authenticator-App oder einen Ihrer einmaligen Backup-Codes ein.",
"otpAuthSubmit": "Code absenden",
"idpContinue": "Oder weiter mit",
"otpAuthBack": "Zurück zur Anmeldung",
"otpAuthBack": "Back to Password",
"navbar": "Navigationsmenü",
"navbarDescription": "Hauptnavigationsmenü für die Anwendung",
"navbarDocsLink": "Dokumentation",
@@ -1188,6 +1219,7 @@
"sidebarOverview": "Übersicht",
"sidebarHome": "Zuhause",
"sidebarSites": "Standorte",
"sidebarApprovals": "Approval Requests",
"sidebarResources": "Ressourcen",
"sidebarProxyResources": "Öffentlich",
"sidebarClientResources": "Privat",
@@ -1204,7 +1236,7 @@
"sidebarIdentityProviders": "Identitätsanbieter",
"sidebarLicense": "Lizenz",
"sidebarClients": "Clients",
"sidebarUserDevices": "Benutzergeräte",
"sidebarUserDevices": "User Devices",
"sidebarMachineClients": "Maschinen",
"sidebarDomains": "Domänen",
"sidebarGeneral": "Verwalten",
@@ -1303,6 +1335,7 @@
"refreshError": "Datenaktualisierung fehlgeschlagen",
"verified": "Verifiziert",
"pending": "Ausstehend",
"pendingApproval": "Pending Approval",
"sidebarBilling": "Abrechnung",
"billing": "Abrechnung",
"orgBillingDescription": "Zahlungsinformationen und Abonnements verwalten",
@@ -1419,7 +1452,7 @@
"securityKeyRemoveSuccess": "Sicherheitsschlüssel erfolgreich entfernt",
"securityKeyRemoveError": "Fehler beim Entfernen des Sicherheitsschlüssels",
"securityKeyLoadError": "Fehler beim Laden der Sicherheitsschlüssel",
"securityKeyLogin": "Mit dem Sicherheitsschlüssel fortfahren",
"securityKeyLogin": "Use Security Key",
"securityKeyAuthError": "Fehler bei der Authentifizierung mit Sicherheitsschlüssel",
"securityKeyRecommendation": "Erwägen Sie die Registrierung eines weiteren Sicherheitsschlüssels auf einem anderen Gerät, um sicherzustellen, dass Sie sich nicht aus Ihrem Konto aussperren.",
"registering": "Registrierung...",
@@ -1479,7 +1512,7 @@
"IAgreeToThe": "Ich stimme den",
"termsOfService": "Nutzungsbedingungen zu",
"and": "und",
"privacyPolicy": "Datenschutzrichtlinie"
"privacyPolicy": "datenschutzrichtlinie."
},
"signUpMarketing": {
"keepMeInTheLoop": "Halten Sie mich auf dem Laufenden mit Neuigkeiten, Updates und neuen Funktionen per E-Mail."
@@ -1546,6 +1579,8 @@
"IntervalSeconds": "Gesunder Intervall",
"timeoutSeconds": "Timeout (Sek.)",
"timeIsInSeconds": "Zeit ist in Sekunden",
"requireDeviceApproval": "Require Device Approvals",
"requireDeviceApprovalDescription": "Users with this role need their devices approved by an admin before they can access resources",
"retryAttempts": "Wiederholungsversuche",
"expectedResponseCodes": "Erwartete Antwortcodes",
"expectedResponseCodesDescription": "HTTP-Statuscode, der einen gesunden Zustand anzeigt. Wenn leer gelassen, wird 200-300 als gesund angesehen.",
@@ -1875,7 +1910,7 @@
"orgAuthChooseIdpDescription": "Wähle deinen Identitätsanbieter um fortzufahren",
"orgAuthNoIdpConfigured": "Diese Organisation hat keine Identitätsanbieter konfiguriert. Sie können sich stattdessen mit Ihrer Pangolin-Identität anmelden.",
"orgAuthSignInWithPangolin": "Mit Pangolin anmelden",
"orgAuthSignInToOrg": "Bei einer Organisation anmelden",
"orgAuthSignInToOrg": "Sign in to an organization",
"orgAuthSelectOrgTitle": "Organisations-Anmeldung",
"orgAuthSelectOrgDescription": "Geben Sie Ihre Organisations-ID ein, um fortzufahren",
"orgAuthOrgIdPlaceholder": "Ihre Organisation",
@@ -2231,6 +2266,8 @@
"deviceCodeInvalidFormat": "Code muss 9 Zeichen lang sein (z.B. A1AJ-N5JD)",
"deviceCodeInvalidOrExpired": "Ungültiger oder abgelaufener Code",
"deviceCodeVerifyFailed": "Fehler beim Überprüfen des Gerätecodes",
"deviceCodeValidating": "Validating device code...",
"deviceCodeVerifying": "Verifying device authorization...",
"signedInAs": "Angemeldet als",
"deviceCodeEnterPrompt": "Geben Sie den auf dem Gerät angezeigten Code ein",
"continue": "Weiter",
@@ -2243,7 +2280,7 @@
"deviceOrganizationsAccess": "Zugriff auf alle Organisationen, auf die Ihr Konto Zugriff hat",
"deviceAuthorize": "{applicationName} autorisieren",
"deviceConnected": "Gerät verbunden!",
"deviceAuthorizedMessage": "Gerät ist berechtigt, auf Ihr Konto zuzugreifen.",
"deviceAuthorizedMessage": "Gerät ist berechtigt, auf Ihr Konto zuzugreifen. Bitte kehren Sie zur Client-Anwendung zurück.",
"pangolinCloud": "Pangolin Cloud",
"viewDevices": "Geräte anzeigen",
"viewDevicesDescription": "Verwalten Sie Ihre verbundenen Geräte",
@@ -2305,6 +2342,7 @@
"identifier": "Identifier",
"deviceLoginUseDifferentAccount": "Nicht du? Verwenden Sie ein anderes Konto.",
"deviceLoginDeviceRequestingAccessToAccount": "Ein Gerät fordert Zugriff auf dieses Konto an.",
"loginSelectAuthenticationMethod": "Select an authentication method to continue.",
"noData": "Keine Daten",
"machineClients": "Maschinen-Clients",
"install": "Installieren",
@@ -2349,6 +2387,7 @@
"enterConfirmation": "Bestätigung eingeben",
"blueprintViewDetails": "Details",
"defaultIdentityProvider": "Standard Identitätsanbieter",
"defaultIdentityProviderDescription": "Wenn ein Standard-Identity Provider ausgewählt ist, wird der Benutzer zur Authentifizierung automatisch an den Anbieter weitergeleitet.",
"editInternalResourceDialogNetworkSettings": "Netzwerkeinstellungen",
"editInternalResourceDialogAccessPolicy": "Zugriffsrichtlinie",
"editInternalResourceDialogAddRoles": "Rollen hinzufügen",
@@ -2392,5 +2431,56 @@
"maintenanceScreenTitle": "Dienst vorübergehend nicht verfügbar",
"maintenanceScreenMessage": "Wir haben derzeit technische Schwierigkeiten. Bitte schauen Sie bald noch einmal vorbei.",
"maintenanceScreenEstimatedCompletion": "Geschätzter Abschluss:",
"createInternalResourceDialogDestinationRequired": "Ziel ist erforderlich"
"createInternalResourceDialogDestinationRequired": "Ziel ist erforderlich",
"available": "Verfügbar",
"archived": "Archiviert",
"noArchivedDevices": "Keine archivierten Geräte gefunden",
"deviceArchived": "Gerät archiviert",
"deviceArchivedDescription": "Das Gerät wurde erfolgreich archiviert.",
"errorArchivingDevice": "Fehler beim Archivieren des Geräts",
"failedToArchiveDevice": "Archivierung des Geräts fehlgeschlagen",
"deviceQuestionArchive": "Sind Sie sicher, dass Sie dieses Gerät archivieren möchten?",
"deviceMessageArchive": "Das Gerät wird archiviert und aus Ihrer Liste der aktiven Geräte entfernt.",
"deviceArchiveConfirm": "Gerät archivieren",
"archiveDevice": "Gerät archivieren",
"archive": "Archiv",
"deviceUnarchived": "Device unarchived",
"deviceUnarchivedDescription": "The device has been successfully unarchived.",
"errorUnarchivingDevice": "Error unarchiving device",
"failedToUnarchiveDevice": "Failed to unarchive device",
"unarchive": "Unarchive",
"archiveClient": "Archive Client",
"archiveClientQuestion": "Are you sure you want to archive this client?",
"archiveClientMessage": "The client will be archived and removed from your active clients list.",
"archiveClientConfirm": "Archive Client",
"blockClient": "Block Client",
"blockClientQuestion": "Are you sure you want to block this client?",
"blockClientMessage": "The device will be forced to disconnect if currently connected. You can unblock the device later.",
"blockClientConfirm": "Block Client",
"active": "Active",
"usernameOrEmail": "Username or Email",
"selectYourOrganization": "Select your organization",
"signInTo": "Log in in to",
"signInWithPassword": "Continue with Password",
"noAuthMethodsAvailable": "No authentication methods available for this organization.",
"enterPassword": "Enter your password",
"enterMfaCode": "Enter the code from your authenticator app",
"securityKeyRequired": "Please use your security key to sign in.",
"needToUseAnotherAccount": "Need to use a different account?",
"loginLegalDisclaimer": "By clicking the buttons below, you acknowledge you have read, understand, and agree to the <termsOfService>Terms of Service</termsOfService> and <privacyPolicy>Privacy Policy</privacyPolicy>.",
"termsOfService": "Terms of Service",
"privacyPolicy": "Privacy Policy",
"userNotFoundWithUsername": "No user found with that username.",
"verify": "Verify",
"signIn": "Sign In",
"forgotPassword": "Forgot password?",
"orgSignInTip": "If you've logged in before, you can enter your username or email above to authenticate with your organization's identity provider instead. It's easier!",
"continueAnyway": "Continue anyway",
"dontShowAgain": "Don't show again",
"orgSignInNotice": "Did you know?",
"signupOrgNotice": "Trying to sign in?",
"signupOrgTip": "Are you trying to sign in through your organization's identity provider?",
"signupOrgLink": "Sign in or sign up with your organization instead",
"verifyEmailLogInWithDifferentAccount": "Use a Different Account",
"logIn": "Log In"
}

View File

@@ -850,6 +850,7 @@
"orgPolicyConfig": "Configure access for an organization",
"idpUpdatedDescription": "Identity provider updated successfully",
"redirectUrl": "Redirect URL",
"orgIdpRedirectUrls": "Redirect URLs",
"redirectUrlAbout": "About Redirect URL",
"redirectUrlAboutDescription": "This is the URL to which users will be redirected after authentication. You need to configure this URL in the identity provider's settings.",
"pangolinAuth": "Auth - Pangolin",
@@ -1479,7 +1480,7 @@
"IAgreeToThe": "I agree to the",
"termsOfService": "terms of service",
"and": "and",
"privacyPolicy": "privacy policy"
"privacyPolicy": "privacy policy."
},
"signUpMarketing": {
"keepMeInTheLoop": "Keep me in the loop with news, updates, and new features by email."
@@ -2349,6 +2350,7 @@
"enterConfirmation": "Enter confirmation",
"blueprintViewDetails": "Details",
"defaultIdentityProvider": "Default Identity Provider",
"defaultIdentityProviderDescription": "When a default identity provider is selected, the user will be automatically redirected to the provider for authentication.",
"editInternalResourceDialogNetworkSettings": "Network Settings",
"editInternalResourceDialogAccessPolicy": "Access Policy",
"editInternalResourceDialogAddRoles": "Add Roles",

View File

@@ -257,6 +257,8 @@
"accessRolesSearch": "Buscar roles...",
"accessRolesAdd": "Añadir rol",
"accessRoleDelete": "Eliminar rol",
"accessApprovalsManage": "Manage Approvals",
"accessApprovalsDescription": "Manage approval requests in the organization",
"description": "Descripción",
"inviteTitle": "Invitaciones abiertas",
"inviteDescription": "Administrar invitaciones para que otros usuarios se unan a la organización",
@@ -450,6 +452,18 @@
"selectDuration": "Seleccionar duración",
"selectResource": "Seleccionar Recurso",
"filterByResource": "Filtrar por Recurso",
"selectApprovalState": "Select Approval State",
"filterByApprovalState": "Filter By Approval State",
"approvalListEmpty": "No approvals",
"approvalState": "Approval State",
"approve": "Approve",
"approved": "Approved",
"denied": "Denied",
"deniedApproval": "Denied Approval",
"all": "All",
"deny": "Deny",
"viewDetails": "View Details",
"requestingNewDeviceApproval": "requested a new device",
"resetFilters": "Reiniciar filtros",
"totalBlocked": "Solicitudes bloqueadas por Pangolin",
"totalRequests": "Solicitudes totales",
@@ -729,16 +743,28 @@
"countries": "Países",
"accessRoleCreate": "Crear rol",
"accessRoleCreateDescription": "Crear un nuevo rol para agrupar usuarios y administrar sus permisos.",
"accessRoleEdit": "Edit Role",
"accessRoleEditDescription": "Edit role information.",
"accessRoleCreateSubmit": "Crear rol",
"accessRoleCreated": "Rol creado",
"accessRoleCreatedDescription": "El rol se ha creado correctamente.",
"accessRoleErrorCreate": "Error al crear el rol",
"accessRoleErrorCreateDescription": "Se ha producido un error al crear el rol.",
"accessRoleUpdateSubmit": "Update Role",
"accessRoleUpdated": "Role updated",
"accessRoleUpdatedDescription": "The role has been successfully updated.",
"accessApprovalUpdated": "Approval processed",
"accessApprovalApprovedDescription": "Set Approval Request decision to approved.",
"accessApprovalDeniedDescription": "Set Approval Request decision to denied.",
"accessRoleErrorUpdate": "Failed to update role",
"accessRoleErrorUpdateDescription": "An error occurred while updating the role.",
"accessApprovalErrorUpdate": "Failed to process approval",
"accessApprovalErrorUpdateDescription": "An error occurred while processing the approval.",
"accessRoleErrorNewRequired": "Se requiere un nuevo rol",
"accessRoleErrorRemove": "Error al eliminar el rol",
"accessRoleErrorRemoveDescription": "Ocurrió un error mientras se eliminaba el rol.",
"accessRoleName": "Nombre del Rol",
"accessRoleQuestionRemove": "Estás a punto de eliminar el rol {name} . No puedes deshacer esta acción.",
"accessRoleQuestionRemove": "You're about to delete the `{name}` role. You cannot undo this action.",
"accessRoleRemove": "Quitar rol",
"accessRoleRemoveDescription": "Eliminar un rol de la organización",
"accessRoleRemoveSubmit": "Quitar rol",
@@ -850,6 +876,7 @@
"orgPolicyConfig": "Configurar acceso para una organización",
"idpUpdatedDescription": "Proveedor de identidad actualizado correctamente",
"redirectUrl": "URL de redirección",
"orgIdpRedirectUrls": "Redirigir URL",
"redirectUrlAbout": "Acerca de la URL de redirección",
"redirectUrlAboutDescription": "Esta es la URL a la que los usuarios serán redireccionados después de la autenticación. Necesitas configurar esta URL en la configuración del proveedor de identidad.",
"pangolinAuth": "Autenticación - Pangolin",
@@ -873,7 +900,7 @@
"inviteAlready": "¡Parece que has sido invitado!",
"inviteAlreadyDescription": "Para aceptar la invitación, debes iniciar sesión o crear una cuenta.",
"signupQuestion": "¿Ya tienes una cuenta?",
"login": "Iniciar sesión",
"login": "Log In",
"resourceNotFound": "Recurso no encontrado",
"resourceNotFoundDescription": "El recurso al que intentas acceder no existe.",
"pincodeRequirementsLength": "El PIN debe tener exactamente 6 dígitos",
@@ -953,13 +980,13 @@
"passwordExpiryDescription": "Esta organización requiere que cambies tu contraseña cada {maxDays} días.",
"changePasswordNow": "Cambiar Contraseña Ahora",
"pincodeAuth": "Código de autenticación",
"pincodeSubmit2": "Enviar código",
"pincodeSubmit2": "Submit code",
"passwordResetSubmit": "Reiniciar Solicitud",
"passwordResetAlreadyHaveCode": "Ingresar código",
"passwordResetSmtpRequired": "Póngase en contacto con su administrador",
"passwordResetSmtpRequiredDescription": "Se requiere un código de restablecimiento de contraseña para restablecer su contraseña. Póngase en contacto con su administrador para obtener asistencia.",
"passwordBack": "Volver a la contraseña",
"loginBack": "Volver a iniciar sesión",
"loginBack": "Go back to main login page",
"signup": "Regístrate",
"loginStart": "Inicia sesión para empezar",
"idpOidcTokenValidating": "Validando token OIDC",
@@ -1117,6 +1144,10 @@
"actionUpdateIdpOrg": "Actualizar IDP Org",
"actionCreateClient": "Crear cliente",
"actionDeleteClient": "Eliminar cliente",
"actionArchiveClient": "Archive Client",
"actionUnarchiveClient": "Unarchive Client",
"actionBlockClient": "Block Client",
"actionUnblockClient": "Unblock Client",
"actionUpdateClient": "Actualizar cliente",
"actionListClients": "Listar clientes",
"actionGetClient": "Obtener cliente",
@@ -1133,14 +1164,14 @@
"searchProgress": "Buscar...",
"create": "Crear",
"orgs": "Organizaciones",
"loginError": "Se ha producido un error al iniciar sesión",
"loginRequiredForDevice": "Es necesario iniciar sesión para autenticar tu dispositivo.",
"loginError": "An unexpected error occurred. Please try again.",
"loginRequiredForDevice": "Login is required for your device.",
"passwordForgot": "¿Olvidaste tu contraseña?",
"otpAuth": "Autenticación de dos factores",
"otpAuthDescription": "Introduzca el código de su aplicación de autenticación o uno de sus códigos de copia de seguridad de un solo uso.",
"otpAuthSubmit": "Enviar código",
"idpContinue": "O continuar con",
"otpAuthBack": "Volver a iniciar sesión",
"otpAuthBack": "Back to Password",
"navbar": "Menú de navegación",
"navbarDescription": "Menú de navegación principal para la aplicación",
"navbarDocsLink": "Documentación",
@@ -1188,6 +1219,7 @@
"sidebarOverview": "Resumen",
"sidebarHome": "Inicio",
"sidebarSites": "Sitios",
"sidebarApprovals": "Approval Requests",
"sidebarResources": "Recursos",
"sidebarProxyResources": "Público",
"sidebarClientResources": "Privado",
@@ -1204,7 +1236,7 @@
"sidebarIdentityProviders": "Proveedores de identidad",
"sidebarLicense": "Licencia",
"sidebarClients": "Clientes",
"sidebarUserDevices": "Usuarios",
"sidebarUserDevices": "User Devices",
"sidebarMachineClients": "Máquinas",
"sidebarDomains": "Dominios",
"sidebarGeneral": "Gestionar",
@@ -1303,6 +1335,7 @@
"refreshError": "Error al actualizar datos",
"verified": "Verificado",
"pending": "Pendiente",
"pendingApproval": "Pending Approval",
"sidebarBilling": "Facturación",
"billing": "Facturación",
"orgBillingDescription": "Administrar información de facturación y suscripciones",
@@ -1419,7 +1452,7 @@
"securityKeyRemoveSuccess": "Llave de seguridad eliminada exitosamente",
"securityKeyRemoveError": "Error al eliminar la llave de seguridad",
"securityKeyLoadError": "Error al cargar las llaves de seguridad",
"securityKeyLogin": "Continuar con clave de seguridad",
"securityKeyLogin": "Use Security Key",
"securityKeyAuthError": "Error al autenticar con llave de seguridad",
"securityKeyRecommendation": "Considere registrar otra llave de seguridad en un dispositivo diferente para asegurarse de no quedar bloqueado de su cuenta.",
"registering": "Registrando...",
@@ -1479,7 +1512,7 @@
"IAgreeToThe": "Estoy de acuerdo con los",
"termsOfService": "términos del servicio",
"and": "y",
"privacyPolicy": "política de privacidad"
"privacyPolicy": "política de privacidad."
},
"signUpMarketing": {
"keepMeInTheLoop": "Mantenerme en el bucle con noticias, actualizaciones y nuevas características por correo electrónico."
@@ -1546,6 +1579,8 @@
"IntervalSeconds": "Intervalo Saludable",
"timeoutSeconds": "Tiempo agotado (seg)",
"timeIsInSeconds": "El tiempo está en segundos",
"requireDeviceApproval": "Require Device Approvals",
"requireDeviceApprovalDescription": "Users with this role need their devices approved by an admin before they can access resources",
"retryAttempts": "Intentos de Reintento",
"expectedResponseCodes": "Códigos de respuesta esperados",
"expectedResponseCodesDescription": "Código de estado HTTP que indica un estado saludable. Si se deja en blanco, se considera saludable de 200 a 300.",
@@ -1875,7 +1910,7 @@
"orgAuthChooseIdpDescription": "Elige tu proveedor de identidad para continuar",
"orgAuthNoIdpConfigured": "Esta organización no tiene ningún proveedor de identidad configurado. En su lugar puedes iniciar sesión con tu identidad de Pangolin.",
"orgAuthSignInWithPangolin": "Iniciar sesión con Pangolin",
"orgAuthSignInToOrg": "Iniciar sesión en una organización",
"orgAuthSignInToOrg": "Sign in to an organization",
"orgAuthSelectOrgTitle": "Inicio de sesión de organización",
"orgAuthSelectOrgDescription": "Ingrese el ID de su organización para continuar",
"orgAuthOrgIdPlaceholder": "tu-organización",
@@ -2231,6 +2266,8 @@
"deviceCodeInvalidFormat": "El código debe tener 9 caracteres (por ejemplo, A1AJ-N5JD)",
"deviceCodeInvalidOrExpired": "Código no válido o caducado",
"deviceCodeVerifyFailed": "Error al verificar el código del dispositivo",
"deviceCodeValidating": "Validating device code...",
"deviceCodeVerifying": "Verifying device authorization...",
"signedInAs": "Conectado como",
"deviceCodeEnterPrompt": "Introduzca el código mostrado en el dispositivo",
"continue": "Continuar",
@@ -2243,7 +2280,7 @@
"deviceOrganizationsAccess": "Acceso a todas las organizaciones a las que su cuenta tiene acceso",
"deviceAuthorize": "Autorizar a {applicationName}",
"deviceConnected": "¡Dispositivo conectado!",
"deviceAuthorizedMessage": "El dispositivo está autorizado para acceder a su cuenta.",
"deviceAuthorizedMessage": "Device is authorized to access your account. Please return to the client application.",
"pangolinCloud": "Nube de Pangolin",
"viewDevices": "Ver dispositivos",
"viewDevicesDescription": "Administra tus dispositivos conectados",
@@ -2305,6 +2342,7 @@
"identifier": "Identifier",
"deviceLoginUseDifferentAccount": "¿No tú? Utilice una cuenta diferente.",
"deviceLoginDeviceRequestingAccessToAccount": "Un dispositivo está solicitando acceso a esta cuenta.",
"loginSelectAuthenticationMethod": "Select an authentication method to continue.",
"noData": "Sin datos",
"machineClients": "Clientes de la máquina",
"install": "Instalar",
@@ -2349,6 +2387,7 @@
"enterConfirmation": "Ingresar confirmación",
"blueprintViewDetails": "Detalles",
"defaultIdentityProvider": "Proveedor de identidad predeterminado",
"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",
"editInternalResourceDialogAccessPolicy": "Política de acceso",
"editInternalResourceDialogAddRoles": "Agregar roles",
@@ -2392,5 +2431,56 @@
"maintenanceScreenTitle": "Servicio temporalmente no disponible",
"maintenanceScreenMessage": "Actualmente estamos experimentando dificultades técnicas. Por favor regrese pronto.",
"maintenanceScreenEstimatedCompletion": "Estimado completado:",
"createInternalResourceDialogDestinationRequired": "Se requiere destino"
"createInternalResourceDialogDestinationRequired": "Se requiere destino",
"available": "Available",
"archived": "Archived",
"noArchivedDevices": "No archived devices found",
"deviceArchived": "Device archived",
"deviceArchivedDescription": "The device has been successfully archived.",
"errorArchivingDevice": "Error archiving device",
"failedToArchiveDevice": "Failed to archive device",
"deviceQuestionArchive": "Are you sure you want to archive this device?",
"deviceMessageArchive": "The device will be archived and removed from your active devices list.",
"deviceArchiveConfirm": "Archive Device",
"archiveDevice": "Archive Device",
"archive": "Archive",
"deviceUnarchived": "Device unarchived",
"deviceUnarchivedDescription": "The device has been successfully unarchived.",
"errorUnarchivingDevice": "Error unarchiving device",
"failedToUnarchiveDevice": "Failed to unarchive device",
"unarchive": "Unarchive",
"archiveClient": "Archive Client",
"archiveClientQuestion": "Are you sure you want to archive this client?",
"archiveClientMessage": "The client will be archived and removed from your active clients list.",
"archiveClientConfirm": "Archive Client",
"blockClient": "Block Client",
"blockClientQuestion": "Are you sure you want to block this client?",
"blockClientMessage": "The device will be forced to disconnect if currently connected. You can unblock the device later.",
"blockClientConfirm": "Block Client",
"active": "Active",
"usernameOrEmail": "Username or Email",
"selectYourOrganization": "Select your organization",
"signInTo": "Log in in to",
"signInWithPassword": "Continue with Password",
"noAuthMethodsAvailable": "No authentication methods available for this organization.",
"enterPassword": "Enter your password",
"enterMfaCode": "Enter the code from your authenticator app",
"securityKeyRequired": "Please use your security key to sign in.",
"needToUseAnotherAccount": "Need to use a different account?",
"loginLegalDisclaimer": "By clicking the buttons below, you acknowledge you have read, understand, and agree to the <termsOfService>Terms of Service</termsOfService> and <privacyPolicy>Privacy Policy</privacyPolicy>.",
"termsOfService": "Terms of Service",
"privacyPolicy": "Privacy Policy",
"userNotFoundWithUsername": "No user found with that username.",
"verify": "Verify",
"signIn": "Sign In",
"forgotPassword": "Forgot password?",
"orgSignInTip": "If you've logged in before, you can enter your username or email above to authenticate with your organization's identity provider instead. It's easier!",
"continueAnyway": "Continue anyway",
"dontShowAgain": "Don't show again",
"orgSignInNotice": "Did you know?",
"signupOrgNotice": "Trying to sign in?",
"signupOrgTip": "Are you trying to sign in through your organization's identity provider?",
"signupOrgLink": "Sign in or sign up with your organization instead",
"verifyEmailLogInWithDifferentAccount": "Use a Different Account",
"logIn": "Log In"
}

View File

@@ -257,6 +257,8 @@
"accessRolesSearch": "Chercher des rôles...",
"accessRolesAdd": "Ajouter un rôle",
"accessRoleDelete": "Supprimer le rôle",
"accessApprovalsManage": "Manage Approvals",
"accessApprovalsDescription": "Manage approval requests in the organization",
"description": "Libellé",
"inviteTitle": "Invitations actives",
"inviteDescription": "Gérer les invitations des autres utilisateurs à rejoindre l'organisation",
@@ -450,6 +452,18 @@
"selectDuration": "Sélectionner la durée",
"selectResource": "Sélectionner une ressource",
"filterByResource": "Filtrer par ressource",
"selectApprovalState": "Select Approval State",
"filterByApprovalState": "Filter By Approval State",
"approvalListEmpty": "No approvals",
"approvalState": "Approval State",
"approve": "Approve",
"approved": "Approved",
"denied": "Denied",
"deniedApproval": "Denied Approval",
"all": "All",
"deny": "Deny",
"viewDetails": "View Details",
"requestingNewDeviceApproval": "requested a new device",
"resetFilters": "Réinitialiser les filtres",
"totalBlocked": "Demandes bloquées par le Pangolin",
"totalRequests": "Total des demandes",
@@ -729,16 +743,28 @@
"countries": "Pays",
"accessRoleCreate": "Créer un rôle",
"accessRoleCreateDescription": "Créer un nouveau rôle pour regrouper les utilisateurs et gérer leurs permissions.",
"accessRoleEdit": "Edit Role",
"accessRoleEditDescription": "Edit role information.",
"accessRoleCreateSubmit": "Créer un rôle",
"accessRoleCreated": "Rôle créé",
"accessRoleCreatedDescription": "Le rôle a été créé avec succès.",
"accessRoleErrorCreate": "Échec de la création du rôle",
"accessRoleErrorCreateDescription": "Une erreur s'est produite lors de la création du rôle.",
"accessRoleUpdateSubmit": "Update Role",
"accessRoleUpdated": "Role updated",
"accessRoleUpdatedDescription": "The role has been successfully updated.",
"accessApprovalUpdated": "Approval processed",
"accessApprovalApprovedDescription": "Set Approval Request decision to approved.",
"accessApprovalDeniedDescription": "Set Approval Request decision to denied.",
"accessRoleErrorUpdate": "Failed to update role",
"accessRoleErrorUpdateDescription": "An error occurred while updating the role.",
"accessApprovalErrorUpdate": "Failed to process approval",
"accessApprovalErrorUpdateDescription": "An error occurred while processing the approval.",
"accessRoleErrorNewRequired": "Un nouveau rôle est requis",
"accessRoleErrorRemove": "Échec de la suppression du rôle",
"accessRoleErrorRemoveDescription": "Une erreur s'est produite lors de la suppression du rôle.",
"accessRoleName": "Nom du rôle",
"accessRoleQuestionRemove": "Vous êtes sur le point de supprimer le rôle {name}. Cette action est irréversible.",
"accessRoleQuestionRemove": "You're about to delete the `{name}` role. You cannot undo this action.",
"accessRoleRemove": "Supprimer le rôle",
"accessRoleRemoveDescription": "Retirer un rôle de l'organisation",
"accessRoleRemoveSubmit": "Supprimer le rôle",
@@ -850,6 +876,7 @@
"orgPolicyConfig": "Configurer l'accès pour une organisation",
"idpUpdatedDescription": "Fournisseur d'identité mis à jour avec succès",
"redirectUrl": "URL de redirection",
"orgIdpRedirectUrls": "URL de redirection",
"redirectUrlAbout": "À propos de l'URL de redirection",
"redirectUrlAboutDescription": "C'est l'URL vers laquelle les utilisateurs seront redirigés après l'authentification. Vous devez configurer cette URL dans les paramètres du fournisseur d'identité.",
"pangolinAuth": "Auth - Pangolin",
@@ -873,7 +900,7 @@
"inviteAlready": "On dirait que vous avez été invité !",
"inviteAlreadyDescription": "Pour accepter l'invitation, vous devez vous connecter ou créer un compte.",
"signupQuestion": "Vous avez déjà un compte ?",
"login": "Se connecter",
"login": "Log In",
"resourceNotFound": "Ressource introuvable",
"resourceNotFoundDescription": "La ressource que vous essayez d'accéder n'existe pas.",
"pincodeRequirementsLength": "Le code PIN doit comporter exactement 6 chiffres",
@@ -953,13 +980,13 @@
"passwordExpiryDescription": "Cette organisation vous demande de changer votre mot de passe tous les {maxDays} jours.",
"changePasswordNow": "Changer le mot de passe maintenant",
"pincodeAuth": "Code d'authentification",
"pincodeSubmit2": "Soumettre le code",
"pincodeSubmit2": "Submit code",
"passwordResetSubmit": "Demander la réinitialisation",
"passwordResetAlreadyHaveCode": "Entrer le code",
"passwordResetSmtpRequired": "Veuillez contacter votre administrateur",
"passwordResetSmtpRequiredDescription": "Un code de réinitialisation du mot de passe est requis pour réinitialiser votre mot de passe. Veuillez contacter votre administrateur pour obtenir de l'aide.",
"passwordBack": "Retour au mot de passe",
"loginBack": "Retour à la connexion",
"loginBack": "Go back to main login page",
"signup": "S'inscrire",
"loginStart": "Connectez-vous pour commencer",
"idpOidcTokenValidating": "Validation du jeton OIDC",
@@ -1117,6 +1144,10 @@
"actionUpdateIdpOrg": "Mettre à jour une organisation IDP",
"actionCreateClient": "Créer un client",
"actionDeleteClient": "Supprimer le client",
"actionArchiveClient": "Archive Client",
"actionUnarchiveClient": "Unarchive Client",
"actionBlockClient": "Block Client",
"actionUnblockClient": "Unblock Client",
"actionUpdateClient": "Mettre à jour le client",
"actionListClients": "Liste des clients",
"actionGetClient": "Obtenir le client",
@@ -1133,14 +1164,14 @@
"searchProgress": "Rechercher...",
"create": "Créer",
"orgs": "Organisations",
"loginError": "Une erreur s'est produite lors de la connexion",
"loginRequiredForDevice": "La connexion est requise pour authentifier votre appareil.",
"loginError": "An unexpected error occurred. Please try again.",
"loginRequiredForDevice": "Login is required for your device.",
"passwordForgot": "Mot de passe oublié ?",
"otpAuth": "Authentification à deux facteurs",
"otpAuthDescription": "Entrez le code de votre application d'authentification ou l'un de vos codes de secours à usage unique.",
"otpAuthSubmit": "Soumettre le code",
"idpContinue": "Ou continuer avec",
"otpAuthBack": "Retour à la connexion",
"otpAuthBack": "Back to Password",
"navbar": "Menu de navigation",
"navbarDescription": "Menu de navigation principal de l'application",
"navbarDocsLink": "Documentation",
@@ -1188,6 +1219,7 @@
"sidebarOverview": "Aperçu",
"sidebarHome": "Domicile",
"sidebarSites": "Nœuds",
"sidebarApprovals": "Approval Requests",
"sidebarResources": "Ressource",
"sidebarProxyResources": "Publique",
"sidebarClientResources": "Privé",
@@ -1204,7 +1236,7 @@
"sidebarIdentityProviders": "Fournisseurs d'identité",
"sidebarLicense": "Licence",
"sidebarClients": "Clients",
"sidebarUserDevices": "Utilisateurs",
"sidebarUserDevices": "User Devices",
"sidebarMachineClients": "Machines",
"sidebarDomains": "Domaines",
"sidebarGeneral": "Gérer",
@@ -1303,6 +1335,7 @@
"refreshError": "Échec de l'actualisation des données",
"verified": "Vérifié",
"pending": "En attente",
"pendingApproval": "Pending Approval",
"sidebarBilling": "Facturation",
"billing": "Facturation",
"orgBillingDescription": "Gérer les informations de facturation et les abonnements",
@@ -1419,7 +1452,7 @@
"securityKeyRemoveSuccess": "Clé de sécurité supprimée avec succès",
"securityKeyRemoveError": "Échec de la suppression de la clé de sécurité",
"securityKeyLoadError": "Échec du chargement des clés de sécurité",
"securityKeyLogin": "Continuer avec une clé de sécurité",
"securityKeyLogin": "Use Security Key",
"securityKeyAuthError": "Échec de l'authentification avec la clé de sécurité",
"securityKeyRecommendation": "Envisagez d'enregistrer une autre clé de sécurité sur un appareil différent pour vous assurer de ne pas être bloqué de votre compte.",
"registering": "Enregistrement...",
@@ -1479,7 +1512,7 @@
"IAgreeToThe": "Je suis d'accord avec",
"termsOfService": "les conditions d'utilisation",
"and": "et",
"privacyPolicy": "la politique de confidentialité"
"privacyPolicy": "politique de confidentialité."
},
"signUpMarketing": {
"keepMeInTheLoop": "Gardez-moi dans la boucle avec des nouvelles, des mises à jour et de nouvelles fonctionnalités par courriel."
@@ -1546,6 +1579,8 @@
"IntervalSeconds": "Intervalle sain",
"timeoutSeconds": "Délai d'attente (sec)",
"timeIsInSeconds": "Le temps est exprimé en secondes",
"requireDeviceApproval": "Require Device Approvals",
"requireDeviceApprovalDescription": "Users with this role need their devices approved by an admin before they can access resources",
"retryAttempts": "Tentatives de réessai",
"expectedResponseCodes": "Codes de réponse attendus",
"expectedResponseCodesDescription": "Code de statut HTTP indiquant un état de santé satisfaisant. Si non renseigné, 200-300 est considéré comme satisfaisant.",
@@ -1875,7 +1910,7 @@
"orgAuthChooseIdpDescription": "Choisissez votre fournisseur d'identité pour continuer",
"orgAuthNoIdpConfigured": "Cette organisation n'a aucun fournisseur d'identité configuré. Vous pouvez vous connecter avec votre identité Pangolin à la place.",
"orgAuthSignInWithPangolin": "Se connecter avec Pangolin",
"orgAuthSignInToOrg": "Connectez-vous à une organisation",
"orgAuthSignInToOrg": "Sign in to an organization",
"orgAuthSelectOrgTitle": "Connexion à l'organisation",
"orgAuthSelectOrgDescription": "Entrez votre identifiant d'organisation pour continuer",
"orgAuthOrgIdPlaceholder": "votre-organisation",
@@ -2231,6 +2266,8 @@
"deviceCodeInvalidFormat": "Le code doit contenir 9 caractères (par exemple, A1AJ-N5JD)",
"deviceCodeInvalidOrExpired": "Code invalide ou expiré",
"deviceCodeVerifyFailed": "Impossible de vérifier le code de l'appareil",
"deviceCodeValidating": "Validating device code...",
"deviceCodeVerifying": "Verifying device authorization...",
"signedInAs": "Connecté en tant que",
"deviceCodeEnterPrompt": "Entrez le code affiché sur l'appareil",
"continue": "Continuer",
@@ -2243,7 +2280,7 @@
"deviceOrganizationsAccess": "Accès à toutes les organisations auxquelles votre compte a accès",
"deviceAuthorize": "Autoriser {applicationName}",
"deviceConnected": "Appareil connecté !",
"deviceAuthorizedMessage": "L'appareil est autorisé à accéder à votre compte.",
"deviceAuthorizedMessage": "Device is authorized to access your account. Please return to the client application.",
"pangolinCloud": "Nuage de Pangolin",
"viewDevices": "Voir les appareils",
"viewDevicesDescription": "Gérer vos appareils connectés",
@@ -2305,6 +2342,7 @@
"identifier": "Identifiant",
"deviceLoginUseDifferentAccount": "Pas vous ? Utilisez un autre compte.",
"deviceLoginDeviceRequestingAccessToAccount": "Un appareil demande l'accès à ce compte.",
"loginSelectAuthenticationMethod": "Select an authentication method to continue.",
"noData": "Aucune donnée",
"machineClients": "Clients Machines",
"install": "Installer",
@@ -2349,6 +2387,7 @@
"enterConfirmation": "Entrez la confirmation",
"blueprintViewDetails": "Détails",
"defaultIdentityProvider": "Fournisseur d'identité par défaut",
"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",
"editInternalResourceDialogAccessPolicy": "Politique d'accès",
"editInternalResourceDialogAddRoles": "Ajouter des rôles",
@@ -2392,5 +2431,56 @@
"maintenanceScreenTitle": "Service temporairement indisponible",
"maintenanceScreenMessage": "Nous rencontrons actuellement des difficultés techniques. Veuillez vérifier ultérieurement.",
"maintenanceScreenEstimatedCompletion": "Achèvement estimé :",
"createInternalResourceDialogDestinationRequired": "La destination est requise"
"createInternalResourceDialogDestinationRequired": "La destination est requise",
"available": "Available",
"archived": "Archived",
"noArchivedDevices": "No archived devices found",
"deviceArchived": "Device archived",
"deviceArchivedDescription": "The device has been successfully archived.",
"errorArchivingDevice": "Error archiving device",
"failedToArchiveDevice": "Failed to archive device",
"deviceQuestionArchive": "Are you sure you want to archive this device?",
"deviceMessageArchive": "The device will be archived and removed from your active devices list.",
"deviceArchiveConfirm": "Archive Device",
"archiveDevice": "Archive Device",
"archive": "Archive",
"deviceUnarchived": "Device unarchived",
"deviceUnarchivedDescription": "The device has been successfully unarchived.",
"errorUnarchivingDevice": "Error unarchiving device",
"failedToUnarchiveDevice": "Failed to unarchive device",
"unarchive": "Unarchive",
"archiveClient": "Archive Client",
"archiveClientQuestion": "Are you sure you want to archive this client?",
"archiveClientMessage": "The client will be archived and removed from your active clients list.",
"archiveClientConfirm": "Archive Client",
"blockClient": "Block Client",
"blockClientQuestion": "Are you sure you want to block this client?",
"blockClientMessage": "The device will be forced to disconnect if currently connected. You can unblock the device later.",
"blockClientConfirm": "Block Client",
"active": "Active",
"usernameOrEmail": "Username or Email",
"selectYourOrganization": "Select your organization",
"signInTo": "Log in in to",
"signInWithPassword": "Continue with Password",
"noAuthMethodsAvailable": "No authentication methods available for this organization.",
"enterPassword": "Enter your password",
"enterMfaCode": "Enter the code from your authenticator app",
"securityKeyRequired": "Please use your security key to sign in.",
"needToUseAnotherAccount": "Need to use a different account?",
"loginLegalDisclaimer": "By clicking the buttons below, you acknowledge you have read, understand, and agree to the <termsOfService>Terms of Service</termsOfService> and <privacyPolicy>Privacy Policy</privacyPolicy>.",
"termsOfService": "Terms of Service",
"privacyPolicy": "Privacy Policy",
"userNotFoundWithUsername": "No user found with that username.",
"verify": "Verify",
"signIn": "Sign In",
"forgotPassword": "Forgot password?",
"orgSignInTip": "If you've logged in before, you can enter your username or email above to authenticate with your organization's identity provider instead. It's easier!",
"continueAnyway": "Continue anyway",
"dontShowAgain": "Don't show again",
"orgSignInNotice": "Did you know?",
"signupOrgNotice": "Trying to sign in?",
"signupOrgTip": "Are you trying to sign in through your organization's identity provider?",
"signupOrgLink": "Sign in or sign up with your organization instead",
"verifyEmailLogInWithDifferentAccount": "Use a Different Account",
"logIn": "Log In"
}

View File

@@ -257,6 +257,8 @@
"accessRolesSearch": "Ricerca ruoli...",
"accessRolesAdd": "Aggiungi Ruolo",
"accessRoleDelete": "Elimina Ruolo",
"accessApprovalsManage": "Manage Approvals",
"accessApprovalsDescription": "Manage approval requests in the organization",
"description": "Descrizione",
"inviteTitle": "Inviti Aperti",
"inviteDescription": "Gestisci gli inviti per gli altri utenti a unirsi all'organizzazione",
@@ -450,6 +452,18 @@
"selectDuration": "Seleziona durata",
"selectResource": "Seleziona Risorsa",
"filterByResource": "Filtra Per Risorsa",
"selectApprovalState": "Select Approval State",
"filterByApprovalState": "Filter By Approval State",
"approvalListEmpty": "No approvals",
"approvalState": "Approval State",
"approve": "Approve",
"approved": "Approved",
"denied": "Denied",
"deniedApproval": "Denied Approval",
"all": "All",
"deny": "Deny",
"viewDetails": "View Details",
"requestingNewDeviceApproval": "requested a new device",
"resetFilters": "Ripristina Filtri",
"totalBlocked": "Richieste Bloccate Da Pangolino",
"totalRequests": "Totale Richieste",
@@ -729,16 +743,28 @@
"countries": "Paesi",
"accessRoleCreate": "Crea Ruolo",
"accessRoleCreateDescription": "Crea un nuovo ruolo per raggruppare gli utenti e gestire i loro permessi.",
"accessRoleEdit": "Edit Role",
"accessRoleEditDescription": "Edit role information.",
"accessRoleCreateSubmit": "Crea Ruolo",
"accessRoleCreated": "Ruolo creato",
"accessRoleCreatedDescription": "Il ruolo è stato creato con successo.",
"accessRoleErrorCreate": "Impossibile creare il ruolo",
"accessRoleErrorCreateDescription": "Si è verificato un errore durante la creazione del ruolo.",
"accessRoleUpdateSubmit": "Update Role",
"accessRoleUpdated": "Role updated",
"accessRoleUpdatedDescription": "The role has been successfully updated.",
"accessApprovalUpdated": "Approval processed",
"accessApprovalApprovedDescription": "Set Approval Request decision to approved.",
"accessApprovalDeniedDescription": "Set Approval Request decision to denied.",
"accessRoleErrorUpdate": "Failed to update role",
"accessRoleErrorUpdateDescription": "An error occurred while updating the role.",
"accessApprovalErrorUpdate": "Failed to process approval",
"accessApprovalErrorUpdateDescription": "An error occurred while processing the approval.",
"accessRoleErrorNewRequired": "Nuovo ruolo richiesto",
"accessRoleErrorRemove": "Impossibile rimuovere il ruolo",
"accessRoleErrorRemoveDescription": "Si è verificato un errore durante la rimozione del ruolo.",
"accessRoleName": "Nome Del Ruolo",
"accessRoleQuestionRemove": "Stai per eliminare il ruolo {name}. Non puoi annullare questa azione.",
"accessRoleQuestionRemove": "You're about to delete the `{name}` role. You cannot undo this action.",
"accessRoleRemove": "Rimuovi Ruolo",
"accessRoleRemoveDescription": "Rimuovi un ruolo dall'organizzazione",
"accessRoleRemoveSubmit": "Rimuovi Ruolo",
@@ -850,6 +876,7 @@
"orgPolicyConfig": "Configura l'accesso per un'organizzazione",
"idpUpdatedDescription": "Provider di identità aggiornato con successo",
"redirectUrl": "URL di Reindirizzamento",
"orgIdpRedirectUrls": "Reindirizza URL",
"redirectUrlAbout": "Informazioni sull'URL di Reindirizzamento",
"redirectUrlAboutDescription": "Questo è l'URL a cui gli utenti saranno reindirizzati dopo l'autenticazione. È necessario configurare questo URL nelle impostazioni del provider di identità.",
"pangolinAuth": "Autenticazione - Pangolina",
@@ -873,7 +900,7 @@
"inviteAlready": "Sembra che sei stato invitato!",
"inviteAlreadyDescription": "Per accettare l'invito, devi accedere o creare un account.",
"signupQuestion": "Hai già un account?",
"login": "Accedi",
"login": "Log In",
"resourceNotFound": "Risorsa Non Trovata",
"resourceNotFoundDescription": "La risorsa che stai cercando di accedere non esiste.",
"pincodeRequirementsLength": "Il PIN deve essere esattamente di 6 cifre",
@@ -953,13 +980,13 @@
"passwordExpiryDescription": "Questa organizzazione richiede di cambiare la password ogni {maxDays} giorni.",
"changePasswordNow": "Cambia Password Ora",
"pincodeAuth": "Codice Autenticatore",
"pincodeSubmit2": "Invia Codice",
"pincodeSubmit2": "Submit code",
"passwordResetSubmit": "Richiedi Reset",
"passwordResetAlreadyHaveCode": "Inserisci Codice",
"passwordResetSmtpRequired": "Si prega di contattare l'amministratore",
"passwordResetSmtpRequiredDescription": "Per reimpostare la password è necessario un codice di reimpostazione della password. Si prega di contattare l'amministratore per assistenza.",
"passwordBack": "Torna alla Password",
"loginBack": "Torna al login",
"loginBack": "Go back to main login page",
"signup": "Registrati",
"loginStart": "Accedi per iniziare",
"idpOidcTokenValidating": "Convalida token OIDC",
@@ -1117,6 +1144,10 @@
"actionUpdateIdpOrg": "Aggiorna Org IDP",
"actionCreateClient": "Crea Client",
"actionDeleteClient": "Elimina Client",
"actionArchiveClient": "Archive Client",
"actionUnarchiveClient": "Unarchive Client",
"actionBlockClient": "Block Client",
"actionUnblockClient": "Unblock Client",
"actionUpdateClient": "Aggiorna Client",
"actionListClients": "Elenco Clienti",
"actionGetClient": "Ottieni Client",
@@ -1133,14 +1164,14 @@
"searchProgress": "Ricerca...",
"create": "Crea",
"orgs": "Organizzazioni",
"loginError": "Si è verificato un errore durante l'accesso",
"loginRequiredForDevice": "È richiesto il login per autenticare il dispositivo.",
"loginError": "An unexpected error occurred. Please try again.",
"loginRequiredForDevice": "Login is required for your device.",
"passwordForgot": "Password dimenticata?",
"otpAuth": "Autenticazione a Due Fattori",
"otpAuthDescription": "Inserisci il codice dalla tua app di autenticazione o uno dei tuoi codici di backup monouso.",
"otpAuthSubmit": "Invia Codice",
"idpContinue": "O continua con",
"otpAuthBack": "Torna al Login",
"otpAuthBack": "Back to Password",
"navbar": "Menu di Navigazione",
"navbarDescription": "Menu di navigazione principale dell'applicazione",
"navbarDocsLink": "Documentazione",
@@ -1188,6 +1219,7 @@
"sidebarOverview": "Panoramica",
"sidebarHome": "Home",
"sidebarSites": "Siti",
"sidebarApprovals": "Approval Requests",
"sidebarResources": "Risorse",
"sidebarProxyResources": "Pubblico",
"sidebarClientResources": "Privato",
@@ -1204,7 +1236,7 @@
"sidebarIdentityProviders": "Fornitori Di Identità",
"sidebarLicense": "Licenza",
"sidebarClients": "Client",
"sidebarUserDevices": "Utenti",
"sidebarUserDevices": "User Devices",
"sidebarMachineClients": "Macchine",
"sidebarDomains": "Domini",
"sidebarGeneral": "Gestisci",
@@ -1303,6 +1335,7 @@
"refreshError": "Impossibile aggiornare i dati",
"verified": "Verificato",
"pending": "In attesa",
"pendingApproval": "Pending Approval",
"sidebarBilling": "Fatturazione",
"billing": "Fatturazione",
"orgBillingDescription": "Gestisci le informazioni di fatturazione e gli abbonamenti",
@@ -1419,7 +1452,7 @@
"securityKeyRemoveSuccess": "Chiave di sicurezza rimossa con successo",
"securityKeyRemoveError": "Errore durante la rimozione della chiave di sicurezza",
"securityKeyLoadError": "Errore durante il caricamento delle chiavi di sicurezza",
"securityKeyLogin": "Continua con la chiave di sicurezza",
"securityKeyLogin": "Use Security Key",
"securityKeyAuthError": "Errore durante l'autenticazione con chiave di sicurezza",
"securityKeyRecommendation": "Considera di registrare un'altra chiave di sicurezza su un dispositivo diverso per assicurarti di non rimanere bloccato fuori dal tuo account.",
"registering": "Registrazione in corso...",
@@ -1479,7 +1512,7 @@
"IAgreeToThe": "Accetto i",
"termsOfService": "termini di servizio",
"and": "e",
"privacyPolicy": "informativa sulla privacy"
"privacyPolicy": "informativa sulla privacy."
},
"signUpMarketing": {
"keepMeInTheLoop": "Tienimi in loop con notizie, aggiornamenti e nuove funzionalità via e-mail."
@@ -1546,6 +1579,8 @@
"IntervalSeconds": "Intervallo Sano",
"timeoutSeconds": "Timeout (sec)",
"timeIsInSeconds": "Il tempo è in secondi",
"requireDeviceApproval": "Require Device Approvals",
"requireDeviceApprovalDescription": "Users with this role need their devices approved by an admin before they can access resources",
"retryAttempts": "Tentativi di Riprova",
"expectedResponseCodes": "Codici di Risposta Attesi",
"expectedResponseCodesDescription": "Codice di stato HTTP che indica lo stato di salute. Se lasciato vuoto, considerato sano è compreso tra 200-300.",
@@ -1875,7 +1910,7 @@
"orgAuthChooseIdpDescription": "Scegli il tuo provider di identità per continuare",
"orgAuthNoIdpConfigured": "Questa organizzazione non ha nessun provider di identità configurato. Puoi accedere con la tua identità Pangolin.",
"orgAuthSignInWithPangolin": "Accedi con Pangolino",
"orgAuthSignInToOrg": "Accedi a un'organizzazione",
"orgAuthSignInToOrg": "Sign in to an organization",
"orgAuthSelectOrgTitle": "Accesso Organizzazione",
"orgAuthSelectOrgDescription": "Inserisci l'ID dell'organizzazione per continuare",
"orgAuthOrgIdPlaceholder": "la-tua-organizzazione",
@@ -2231,6 +2266,8 @@
"deviceCodeInvalidFormat": "Il codice deve contenere 9 caratteri (es. A1AJ-N5JD)",
"deviceCodeInvalidOrExpired": "Codice non valido o scaduto",
"deviceCodeVerifyFailed": "Impossibile verificare il codice del dispositivo",
"deviceCodeValidating": "Validating device code...",
"deviceCodeVerifying": "Verifying device authorization...",
"signedInAs": "Accesso come",
"deviceCodeEnterPrompt": "Inserisci il codice visualizzato sul dispositivo",
"continue": "Continua",
@@ -2243,7 +2280,7 @@
"deviceOrganizationsAccess": "Accesso a tutte le organizzazioni a cui il tuo account ha accesso",
"deviceAuthorize": "Autorizza {applicationName}",
"deviceConnected": "Dispositivo Connesso!",
"deviceAuthorizedMessage": "Il dispositivo è autorizzato ad accedere al tuo account.",
"deviceAuthorizedMessage": "Device is authorized to access your account. Please return to the client application.",
"pangolinCloud": "Pangolin Cloud",
"viewDevices": "Visualizza Dispositivi",
"viewDevicesDescription": "Gestisci i tuoi dispositivi connessi",
@@ -2305,6 +2342,7 @@
"identifier": "Identifier",
"deviceLoginUseDifferentAccount": "Non tu? Usa un account diverso.",
"deviceLoginDeviceRequestingAccessToAccount": "Un dispositivo sta richiedendo l'accesso a questo account.",
"loginSelectAuthenticationMethod": "Select an authentication method to continue.",
"noData": "Nessun Dato",
"machineClients": "Machine Clients",
"install": "Installa",
@@ -2349,6 +2387,7 @@
"enterConfirmation": "Inserisci conferma",
"blueprintViewDetails": "Dettagli",
"defaultIdentityProvider": "Provider di Identità Predefinito",
"defaultIdentityProviderDescription": "Quando viene selezionato un provider di identità predefinito, l'utente verrà automaticamente reindirizzato al provider per l'autenticazione.",
"editInternalResourceDialogNetworkSettings": "Impostazioni di Rete",
"editInternalResourceDialogAccessPolicy": "Politica di Accesso",
"editInternalResourceDialogAddRoles": "Aggiungi Ruoli",
@@ -2392,5 +2431,56 @@
"maintenanceScreenTitle": "Servizio Temporaneamente Non Disponibile",
"maintenanceScreenMessage": "Stiamo attualmente riscontrando difficoltà tecniche. Si prega di ricontrollare a breve.",
"maintenanceScreenEstimatedCompletion": "Completamento Stimato:",
"createInternalResourceDialogDestinationRequired": "Destinazione richiesta"
"createInternalResourceDialogDestinationRequired": "Destinazione richiesta",
"available": "Available",
"archived": "Archived",
"noArchivedDevices": "No archived devices found",
"deviceArchived": "Device archived",
"deviceArchivedDescription": "The device has been successfully archived.",
"errorArchivingDevice": "Error archiving device",
"failedToArchiveDevice": "Failed to archive device",
"deviceQuestionArchive": "Are you sure you want to archive this device?",
"deviceMessageArchive": "The device will be archived and removed from your active devices list.",
"deviceArchiveConfirm": "Archive Device",
"archiveDevice": "Archive Device",
"archive": "Archive",
"deviceUnarchived": "Device unarchived",
"deviceUnarchivedDescription": "The device has been successfully unarchived.",
"errorUnarchivingDevice": "Error unarchiving device",
"failedToUnarchiveDevice": "Failed to unarchive device",
"unarchive": "Unarchive",
"archiveClient": "Archive Client",
"archiveClientQuestion": "Are you sure you want to archive this client?",
"archiveClientMessage": "The client will be archived and removed from your active clients list.",
"archiveClientConfirm": "Archive Client",
"blockClient": "Block Client",
"blockClientQuestion": "Are you sure you want to block this client?",
"blockClientMessage": "The device will be forced to disconnect if currently connected. You can unblock the device later.",
"blockClientConfirm": "Block Client",
"active": "Active",
"usernameOrEmail": "Username or Email",
"selectYourOrganization": "Select your organization",
"signInTo": "Log in in to",
"signInWithPassword": "Continue with Password",
"noAuthMethodsAvailable": "No authentication methods available for this organization.",
"enterPassword": "Enter your password",
"enterMfaCode": "Enter the code from your authenticator app",
"securityKeyRequired": "Please use your security key to sign in.",
"needToUseAnotherAccount": "Need to use a different account?",
"loginLegalDisclaimer": "By clicking the buttons below, you acknowledge you have read, understand, and agree to the <termsOfService>Terms of Service</termsOfService> and <privacyPolicy>Privacy Policy</privacyPolicy>.",
"termsOfService": "Terms of Service",
"privacyPolicy": "Privacy Policy",
"userNotFoundWithUsername": "No user found with that username.",
"verify": "Verify",
"signIn": "Sign In",
"forgotPassword": "Forgot password?",
"orgSignInTip": "If you've logged in before, you can enter your username or email above to authenticate with your organization's identity provider instead. It's easier!",
"continueAnyway": "Continue anyway",
"dontShowAgain": "Don't show again",
"orgSignInNotice": "Did you know?",
"signupOrgNotice": "Trying to sign in?",
"signupOrgTip": "Are you trying to sign in through your organization's identity provider?",
"signupOrgLink": "Sign in or sign up with your organization instead",
"verifyEmailLogInWithDifferentAccount": "Use a Different Account",
"logIn": "Log In"
}

View File

@@ -257,6 +257,8 @@
"accessRolesSearch": "역할 검색...",
"accessRolesAdd": "역할 추가",
"accessRoleDelete": "역할 삭제",
"accessApprovalsManage": "Manage Approvals",
"accessApprovalsDescription": "Manage approval requests in the organization",
"description": "설명",
"inviteTitle": "열린 초대",
"inviteDescription": "다른 사용자가 조직에 참여하도록 초대장을 관리합니다.",
@@ -450,6 +452,18 @@
"selectDuration": "지속 시간 선택",
"selectResource": "리소스 선택",
"filterByResource": "리소스별 필터",
"selectApprovalState": "Select Approval State",
"filterByApprovalState": "Filter By Approval State",
"approvalListEmpty": "No approvals",
"approvalState": "Approval State",
"approve": "Approve",
"approved": "Approved",
"denied": "Denied",
"deniedApproval": "Denied Approval",
"all": "All",
"deny": "Deny",
"viewDetails": "View Details",
"requestingNewDeviceApproval": "requested a new device",
"resetFilters": "필터 재설정",
"totalBlocked": "Pangolin으로 차단된 요청",
"totalRequests": "총 요청 수",
@@ -729,16 +743,28 @@
"countries": "국가",
"accessRoleCreate": "역할 생성",
"accessRoleCreateDescription": "사용자를 그룹화하고 권한을 관리하기 위해 새 역할을 생성하세요.",
"accessRoleEdit": "Edit Role",
"accessRoleEditDescription": "Edit role information.",
"accessRoleCreateSubmit": "역할 생성",
"accessRoleCreated": "역할이 생성되었습니다.",
"accessRoleCreatedDescription": "역할이 성공적으로 생성되었습니다.",
"accessRoleErrorCreate": "역할 생성 실패",
"accessRoleErrorCreateDescription": "역할 생성 중 오류가 발생했습니다.",
"accessRoleUpdateSubmit": "Update Role",
"accessRoleUpdated": "Role updated",
"accessRoleUpdatedDescription": "The role has been successfully updated.",
"accessApprovalUpdated": "Approval processed",
"accessApprovalApprovedDescription": "Set Approval Request decision to approved.",
"accessApprovalDeniedDescription": "Set Approval Request decision to denied.",
"accessRoleErrorUpdate": "Failed to update role",
"accessRoleErrorUpdateDescription": "An error occurred while updating the role.",
"accessApprovalErrorUpdate": "Failed to process approval",
"accessApprovalErrorUpdateDescription": "An error occurred while processing the approval.",
"accessRoleErrorNewRequired": "새 역할이 필요합니다.",
"accessRoleErrorRemove": "역할 제거에 실패했습니다.",
"accessRoleErrorRemoveDescription": "역할을 제거하는 동안 오류가 발생했습니다.",
"accessRoleName": "역할 이름",
"accessRoleQuestionRemove": "{name} 역할을 삭제하려고 합니다. 이 작업은 취소할 수 없습니다.",
"accessRoleQuestionRemove": "You're about to delete the `{name}` role. You cannot undo this action.",
"accessRoleRemove": "역할 제거",
"accessRoleRemoveDescription": "조직에서 역할 제거",
"accessRoleRemoveSubmit": "역할 제거",
@@ -850,6 +876,7 @@
"orgPolicyConfig": "조직에 대한 접근을 구성하십시오.",
"idpUpdatedDescription": "아이덴티티 제공자가 성공적으로 업데이트되었습니다",
"redirectUrl": "리디렉션 URL",
"orgIdpRedirectUrls": "리디렉션 URL",
"redirectUrlAbout": "리디렉션 URL에 대한 정보",
"redirectUrlAboutDescription": "사용자가 인증 후 리디렉션될 URL입니다. 이 URL을 신원 제공자 설정에서 구성해야 합니다.",
"pangolinAuth": "인증 - 판골린",
@@ -873,7 +900,7 @@
"inviteAlready": "초대받은 것 같습니다!",
"inviteAlreadyDescription": "초대를 수락하려면 로그인하거나 계정을 생성해야 합니다.",
"signupQuestion": "이미 계정이 있습니까?",
"login": "로그인",
"login": "Log In",
"resourceNotFound": "리소스를 찾을 수 없습니다",
"resourceNotFoundDescription": "접근하려는 리소스가 존재하지 않습니다.",
"pincodeRequirementsLength": "PIN은 정확히 6자리여야 합니다",
@@ -953,13 +980,13 @@
"passwordExpiryDescription": "이 조직은 {maxDays}일마다 비밀번호 변경을 요구합니다.",
"changePasswordNow": "지금 비밀번호 변경",
"pincodeAuth": "인증 코드",
"pincodeSubmit2": "코드 제출",
"pincodeSubmit2": "Submit code",
"passwordResetSubmit": "재설정 요청",
"passwordResetAlreadyHaveCode": "코드를 입력하십시오.",
"passwordResetSmtpRequired": "관리자에게 문의하십시오",
"passwordResetSmtpRequiredDescription": "비밀번호를 재설정하려면 비밀번호 초기화 코드가 필요합니다. 지원을 받으려면 관리자에게 문의하십시오.",
"passwordBack": "비밀번호로 돌아가기",
"loginBack": "로그인으로 돌아가기",
"loginBack": "Go back to main login page",
"signup": "가입하기",
"loginStart": "시작하려면 로그인하세요.",
"idpOidcTokenValidating": "OIDC 토큰 검증 중",
@@ -1117,6 +1144,10 @@
"actionUpdateIdpOrg": "IDP 조직 업데이트",
"actionCreateClient": "클라이언트 생성",
"actionDeleteClient": "클라이언트 삭제",
"actionArchiveClient": "Archive Client",
"actionUnarchiveClient": "Unarchive Client",
"actionBlockClient": "Block Client",
"actionUnblockClient": "Unblock Client",
"actionUpdateClient": "클라이언트 업데이트",
"actionListClients": "클라이언트 목록",
"actionGetClient": "클라이언트 가져오기",
@@ -1133,14 +1164,14 @@
"searchProgress": "검색...",
"create": "생성",
"orgs": "조직",
"loginError": "로그인 중 오류가 발생했습니다",
"loginRequiredForDevice": "장치를 인증하려면 로그인이 필요합니다.",
"loginError": "An unexpected error occurred. Please try again.",
"loginRequiredForDevice": "Login is required for your device.",
"passwordForgot": "비밀번호를 잊으셨나요?",
"otpAuth": "이중 인증",
"otpAuthDescription": "인증 앱에서 코드를 입력하거나 단일 사용 백업 코드 중 하나를 입력하세요.",
"otpAuthSubmit": "코드 제출",
"idpContinue": "또는 계속 진행하십시오.",
"otpAuthBack": "로그인으로 돌아가기",
"otpAuthBack": "Back to Password",
"navbar": "탐색 메뉴",
"navbarDescription": "애플리케이션의 주요 탐색 메뉴",
"navbarDocsLink": "문서",
@@ -1188,6 +1219,7 @@
"sidebarOverview": "개요",
"sidebarHome": "홈",
"sidebarSites": "사이트",
"sidebarApprovals": "Approval Requests",
"sidebarResources": "리소스",
"sidebarProxyResources": "공유",
"sidebarClientResources": "비공개",
@@ -1204,7 +1236,7 @@
"sidebarIdentityProviders": "신원 공급자",
"sidebarLicense": "라이선스",
"sidebarClients": "클라이언트",
"sidebarUserDevices": "사용자",
"sidebarUserDevices": "User Devices",
"sidebarMachineClients": "기계",
"sidebarDomains": "도메인",
"sidebarGeneral": "관리",
@@ -1303,6 +1335,7 @@
"refreshError": "데이터 새로고침 실패",
"verified": "검증됨",
"pending": "대기 중",
"pendingApproval": "Pending Approval",
"sidebarBilling": "청구",
"billing": "청구",
"orgBillingDescription": "청구 정보 및 구독을 관리하세요",
@@ -1419,7 +1452,7 @@
"securityKeyRemoveSuccess": "보안 키가 성공적으로 제거되었습니다",
"securityKeyRemoveError": "보안 키 제거 실패",
"securityKeyLoadError": "보안 키를 불러오는 데 실패했습니다",
"securityKeyLogin": "보안 키로 계속하기",
"securityKeyLogin": "Use Security Key",
"securityKeyAuthError": "보안 키를 사용한 인증 실패",
"securityKeyRecommendation": "항상 계정에 액세스할 수 있도록 다른 장치에 백업 보안 키를 등록하세요.",
"registering": "등록 중...",
@@ -1479,7 +1512,7 @@
"IAgreeToThe": "동의합니다",
"termsOfService": "서비스 약관",
"and": "및",
"privacyPolicy": "개인 정보 보호 정책"
"privacyPolicy": "개인 정보 보호 정책."
},
"signUpMarketing": {
"keepMeInTheLoop": "이메일을 통해 소식, 업데이트 및 새로운 기능을 받아보세요."
@@ -1546,6 +1579,8 @@
"IntervalSeconds": "정상 간격",
"timeoutSeconds": "타임아웃(초)",
"timeIsInSeconds": "시간은 초 단위입니다",
"requireDeviceApproval": "Require Device Approvals",
"requireDeviceApprovalDescription": "Users with this role need their devices approved by an admin before they can access resources",
"retryAttempts": "재시도 횟수",
"expectedResponseCodes": "예상 응답 코드",
"expectedResponseCodesDescription": "정상 상태를 나타내는 HTTP 상태 코드입니다. 비워 두면 200-300이 정상으로 간주됩니다.",
@@ -1875,7 +1910,7 @@
"orgAuthChooseIdpDescription": "계속하려면 신원 공급자를 선택하세요.",
"orgAuthNoIdpConfigured": "이 조직은 구성된 신원 공급자가 없습니다. 대신 Pangolin 아이덴티티로 로그인할 수 있습니다.",
"orgAuthSignInWithPangolin": "Pangolin으로 로그인",
"orgAuthSignInToOrg": "조직에 로그인합니다.",
"orgAuthSignInToOrg": "Sign in to an organization",
"orgAuthSelectOrgTitle": "조직 로그인",
"orgAuthSelectOrgDescription": "계속하려면 조직 ID를 입력하십시오.",
"orgAuthOrgIdPlaceholder": "your-organization",
@@ -2231,6 +2266,8 @@
"deviceCodeInvalidFormat": "코드는 9자리여야 합니다 (예: A1AJ-N5JD)",
"deviceCodeInvalidOrExpired": "무효하거나 만료된 코드",
"deviceCodeVerifyFailed": "이메일 확인에 실패했습니다:",
"deviceCodeValidating": "Validating device code...",
"deviceCodeVerifying": "Verifying device authorization...",
"signedInAs": "로그인한 사용자",
"deviceCodeEnterPrompt": "기기에 표시된 코드를 입력하세요",
"continue": "계속 진행하기",
@@ -2243,7 +2280,7 @@
"deviceOrganizationsAccess": "계정이 접근할 수 있는 모든 조직에 대한 접근",
"deviceAuthorize": "{applicationName} 권한 부여",
"deviceConnected": "장치가 연결되었습니다!",
"deviceAuthorizedMessage": "장치가 계정에 액세스할 수 있도록 승인되었습니다.",
"deviceAuthorizedMessage": "Device is authorized to access your account. Please return to the client application.",
"pangolinCloud": "판골린 클라우드",
"viewDevices": "장치 보기",
"viewDevicesDescription": "연결된 장치를 관리하십시오",
@@ -2305,6 +2342,7 @@
"identifier": "식별자",
"deviceLoginUseDifferentAccount": "본인이 아닙니까? 다른 계정을 사용하세요.",
"deviceLoginDeviceRequestingAccessToAccount": "장치가 이 계정에 접근하려고 합니다.",
"loginSelectAuthenticationMethod": "Select an authentication method to continue.",
"noData": "데이터 없음",
"machineClients": "기계 클라이언트",
"install": "설치",
@@ -2349,6 +2387,7 @@
"enterConfirmation": "확인 입력",
"blueprintViewDetails": "세부 정보",
"defaultIdentityProvider": "기본 아이덴티티 공급자",
"defaultIdentityProviderDescription": "기본 ID 공급자가 선택되면, 사용자는 인증을 위해 자동으로 해당 공급자로 리디렉션됩니다.",
"editInternalResourceDialogNetworkSettings": "네트워크 설정",
"editInternalResourceDialogAccessPolicy": "액세스 정책",
"editInternalResourceDialogAddRoles": "역할 추가",
@@ -2392,5 +2431,56 @@
"maintenanceScreenTitle": "서비스 일시 중단",
"maintenanceScreenMessage": "현재 기술적 문제를 겪고 있습니다. 곧 다시 확인하십시오.",
"maintenanceScreenEstimatedCompletion": "예상 완료:",
"createInternalResourceDialogDestinationRequired": "목적지가 필요합니다."
"createInternalResourceDialogDestinationRequired": "목적지가 필요합니다.",
"available": "Available",
"archived": "Archived",
"noArchivedDevices": "No archived devices found",
"deviceArchived": "Device archived",
"deviceArchivedDescription": "The device has been successfully archived.",
"errorArchivingDevice": "Error archiving device",
"failedToArchiveDevice": "Failed to archive device",
"deviceQuestionArchive": "Are you sure you want to archive this device?",
"deviceMessageArchive": "The device will be archived and removed from your active devices list.",
"deviceArchiveConfirm": "Archive Device",
"archiveDevice": "Archive Device",
"archive": "Archive",
"deviceUnarchived": "Device unarchived",
"deviceUnarchivedDescription": "The device has been successfully unarchived.",
"errorUnarchivingDevice": "Error unarchiving device",
"failedToUnarchiveDevice": "Failed to unarchive device",
"unarchive": "Unarchive",
"archiveClient": "Archive Client",
"archiveClientQuestion": "Are you sure you want to archive this client?",
"archiveClientMessage": "The client will be archived and removed from your active clients list.",
"archiveClientConfirm": "Archive Client",
"blockClient": "Block Client",
"blockClientQuestion": "Are you sure you want to block this client?",
"blockClientMessage": "The device will be forced to disconnect if currently connected. You can unblock the device later.",
"blockClientConfirm": "Block Client",
"active": "Active",
"usernameOrEmail": "Username or Email",
"selectYourOrganization": "Select your organization",
"signInTo": "Log in in to",
"signInWithPassword": "Continue with Password",
"noAuthMethodsAvailable": "No authentication methods available for this organization.",
"enterPassword": "Enter your password",
"enterMfaCode": "Enter the code from your authenticator app",
"securityKeyRequired": "Please use your security key to sign in.",
"needToUseAnotherAccount": "Need to use a different account?",
"loginLegalDisclaimer": "By clicking the buttons below, you acknowledge you have read, understand, and agree to the <termsOfService>Terms of Service</termsOfService> and <privacyPolicy>Privacy Policy</privacyPolicy>.",
"termsOfService": "Terms of Service",
"privacyPolicy": "Privacy Policy",
"userNotFoundWithUsername": "No user found with that username.",
"verify": "Verify",
"signIn": "Sign In",
"forgotPassword": "Forgot password?",
"orgSignInTip": "If you've logged in before, you can enter your username or email above to authenticate with your organization's identity provider instead. It's easier!",
"continueAnyway": "Continue anyway",
"dontShowAgain": "Don't show again",
"orgSignInNotice": "Did you know?",
"signupOrgNotice": "Trying to sign in?",
"signupOrgTip": "Are you trying to sign in through your organization's identity provider?",
"signupOrgLink": "Sign in or sign up with your organization instead",
"verifyEmailLogInWithDifferentAccount": "Use a Different Account",
"logIn": "Log In"
}

View File

@@ -257,6 +257,8 @@
"accessRolesSearch": "Søk etter roller...",
"accessRolesAdd": "Legg til rolle",
"accessRoleDelete": "Slett rolle",
"accessApprovalsManage": "Manage Approvals",
"accessApprovalsDescription": "Manage approval requests in the organization",
"description": "Beskrivelse",
"inviteTitle": "Åpne invitasjoner",
"inviteDescription": "Administrer invitasjoner til andre brukere for å bli med i organisasjonen",
@@ -450,6 +452,18 @@
"selectDuration": "Velg varighet",
"selectResource": "Velg ressurs",
"filterByResource": "Filtrer etter ressurser",
"selectApprovalState": "Select Approval State",
"filterByApprovalState": "Filter By Approval State",
"approvalListEmpty": "No approvals",
"approvalState": "Approval State",
"approve": "Approve",
"approved": "Approved",
"denied": "Denied",
"deniedApproval": "Denied Approval",
"all": "All",
"deny": "Deny",
"viewDetails": "View Details",
"requestingNewDeviceApproval": "requested a new device",
"resetFilters": "Tilbakestill filtre",
"totalBlocked": "Forespørsler blokkert av Pangolin",
"totalRequests": "Totalt antall forespørsler",
@@ -729,16 +743,28 @@
"countries": "Land",
"accessRoleCreate": "Opprett rolle",
"accessRoleCreateDescription": "Opprett en ny rolle for å gruppere brukere og administrere deres tillatelser.",
"accessRoleEdit": "Edit Role",
"accessRoleEditDescription": "Edit role information.",
"accessRoleCreateSubmit": "Opprett rolle",
"accessRoleCreated": "Rolle opprettet",
"accessRoleCreatedDescription": "Rollen er vellykket opprettet.",
"accessRoleErrorCreate": "Klarte ikke å opprette rolle",
"accessRoleErrorCreateDescription": "Det oppstod en feil under opprettelse av rollen.",
"accessRoleUpdateSubmit": "Update Role",
"accessRoleUpdated": "Role updated",
"accessRoleUpdatedDescription": "The role has been successfully updated.",
"accessApprovalUpdated": "Approval processed",
"accessApprovalApprovedDescription": "Set Approval Request decision to approved.",
"accessApprovalDeniedDescription": "Set Approval Request decision to denied.",
"accessRoleErrorUpdate": "Failed to update role",
"accessRoleErrorUpdateDescription": "An error occurred while updating the role.",
"accessApprovalErrorUpdate": "Failed to process approval",
"accessApprovalErrorUpdateDescription": "An error occurred while processing the approval.",
"accessRoleErrorNewRequired": "Ny rolle kreves",
"accessRoleErrorRemove": "Kunne ikke fjerne rolle",
"accessRoleErrorRemoveDescription": "Det oppstod en feil under fjerning av rollen.",
"accessRoleName": "Rollenavn",
"accessRoleQuestionRemove": "Du er i ferd med å slette rollen {name}. Du kan ikke angre denne handlingen.",
"accessRoleQuestionRemove": "You're about to delete the `{name}` role. You cannot undo this action.",
"accessRoleRemove": "Fjern Rolle",
"accessRoleRemoveDescription": "Fjern en rolle fra organisasjonen",
"accessRoleRemoveSubmit": "Fjern Rolle",
@@ -850,6 +876,7 @@
"orgPolicyConfig": "Konfigurer tilgang for en organisasjon",
"idpUpdatedDescription": "Identitetsleverandør vellykket oppdatert",
"redirectUrl": "Omdirigerings-URL",
"orgIdpRedirectUrls": "Omadressere URL'er",
"redirectUrlAbout": "Om omdirigerings-URL",
"redirectUrlAboutDescription": "Dette er URLen som brukere vil bli omdirigert etter autentisering. Du må konfigurere denne URLen i identitetsleverandørens innstillinger.",
"pangolinAuth": "Autentisering - Pangolin",
@@ -873,7 +900,7 @@
"inviteAlready": "Ser ut til at du har blitt invitert!",
"inviteAlreadyDescription": "For å godta invitasjonen, må du logge inn eller opprette en konto.",
"signupQuestion": "Har du allerede en konto?",
"login": "Logg inn",
"login": "Log In",
"resourceNotFound": "Ressurs ikke funnet",
"resourceNotFoundDescription": "Ressursen du prøver å få tilgang til eksisterer ikke.",
"pincodeRequirementsLength": "PIN må være nøyaktig 6 siffer",
@@ -953,13 +980,13 @@
"passwordExpiryDescription": "Denne organisasjonen krever at du bytter passord hver {maxDays} dag.",
"changePasswordNow": "Bytt passord nå",
"pincodeAuth": "Autentiseringskode",
"pincodeSubmit2": "Send inn kode",
"pincodeSubmit2": "Submit code",
"passwordResetSubmit": "Be om tilbakestilling",
"passwordResetAlreadyHaveCode": "Skriv inn koden",
"passwordResetSmtpRequired": "Kontakt din administrator",
"passwordResetSmtpRequiredDescription": "En passord tilbakestillingskode kreves for å tilbakestille passordet. Kontakt systemansvarlig for assistanse.",
"passwordBack": "Tilbake til passord",
"loginBack": "Gå tilbake til innlogging",
"loginBack": "Go back to main login page",
"signup": "Registrer deg",
"loginStart": "Logg inn for å komme i gang",
"idpOidcTokenValidating": "Validerer OIDC-token",
@@ -1117,6 +1144,10 @@
"actionUpdateIdpOrg": "Oppdater IDP-organisasjon",
"actionCreateClient": "Opprett Klient",
"actionDeleteClient": "Slett klient",
"actionArchiveClient": "Archive Client",
"actionUnarchiveClient": "Unarchive Client",
"actionBlockClient": "Block Client",
"actionUnblockClient": "Unblock Client",
"actionUpdateClient": "Oppdater klient",
"actionListClients": "List klienter",
"actionGetClient": "Hent klient",
@@ -1133,14 +1164,14 @@
"searchProgress": "Søker...",
"create": "Opprett",
"orgs": "Organisasjoner",
"loginError": "En feil oppstod under innlogging",
"loginRequiredForDevice": "Innlogging kreves for å godkjenne enheten.",
"loginError": "An unexpected error occurred. Please try again.",
"loginRequiredForDevice": "Login is required for your device.",
"passwordForgot": "Glemt passordet ditt?",
"otpAuth": "Tofaktorautentisering",
"otpAuthDescription": "Skriv inn koden fra autentiseringsappen din eller en av dine engangs reservekoder.",
"otpAuthSubmit": "Send inn kode",
"idpContinue": "Eller fortsett med",
"otpAuthBack": "Tilbake til innlogging",
"otpAuthBack": "Back to Password",
"navbar": "Navigasjonsmeny",
"navbarDescription": "Hovednavigasjonsmeny for applikasjonen",
"navbarDocsLink": "Dokumentasjon",
@@ -1188,6 +1219,7 @@
"sidebarOverview": "Oversikt",
"sidebarHome": "Hjem",
"sidebarSites": "Områder",
"sidebarApprovals": "Approval Requests",
"sidebarResources": "Ressurser",
"sidebarProxyResources": "Offentlig",
"sidebarClientResources": "Privat",
@@ -1204,7 +1236,7 @@
"sidebarIdentityProviders": "Identitetsleverandører",
"sidebarLicense": "Lisens",
"sidebarClients": "Klienter",
"sidebarUserDevices": "Brukere",
"sidebarUserDevices": "User Devices",
"sidebarMachineClients": "Maskiner",
"sidebarDomains": "Domener",
"sidebarGeneral": "Administrer",
@@ -1303,6 +1335,7 @@
"refreshError": "Klarte ikke å oppdatere data",
"verified": "Verifisert",
"pending": "Venter",
"pendingApproval": "Pending Approval",
"sidebarBilling": "Fakturering",
"billing": "Fakturering",
"orgBillingDescription": "Administrer faktureringsinformasjon og abonnementer",
@@ -1419,7 +1452,7 @@
"securityKeyRemoveSuccess": "Sikkerhetsnøkkel fjernet",
"securityKeyRemoveError": "Klarte ikke å fjerne sikkerhetsnøkkel",
"securityKeyLoadError": "Klarte ikke å laste inn sikkerhetsnøkler",
"securityKeyLogin": "Fortsett med sikkerhetsnøkkel",
"securityKeyLogin": "Use Security Key",
"securityKeyAuthError": "Klarte ikke å autentisere med sikkerhetsnøkkel",
"securityKeyRecommendation": "Registrer en reservesikkerhetsnøkkel på en annen enhet for å sikre at du alltid har tilgang til kontoen din.",
"registering": "Registrerer...",
@@ -1479,7 +1512,7 @@
"IAgreeToThe": "Jeg godtar",
"termsOfService": "brukervilkårene",
"and": "og",
"privacyPolicy": "personvernerklæringen"
"privacyPolicy": "retningslinjer for personvern"
},
"signUpMarketing": {
"keepMeInTheLoop": "Hold meg i løken med nyheter, oppdateringer og nye funksjoner via e-post."
@@ -1546,6 +1579,8 @@
"IntervalSeconds": "Sunt intervall",
"timeoutSeconds": "Tidsavbrudd (sek)",
"timeIsInSeconds": "Tid er i sekunder",
"requireDeviceApproval": "Require Device Approvals",
"requireDeviceApprovalDescription": "Users with this role need their devices approved by an admin before they can access resources",
"retryAttempts": "Forsøk på nytt",
"expectedResponseCodes": "Forventede svarkoder",
"expectedResponseCodesDescription": "HTTP-statuskode som indikerer sunn status. Hvis den blir stående tom, regnes 200-300 som sunn.",
@@ -1875,7 +1910,7 @@
"orgAuthChooseIdpDescription": "Velg din identitet leverandør for å fortsette",
"orgAuthNoIdpConfigured": "Denne organisasjonen har ikke noen identitetstjeneste konfigurert. Du kan i stedet logge inn med Pangolin identiteten din.",
"orgAuthSignInWithPangolin": "Logg inn med Pangolin",
"orgAuthSignInToOrg": "Logg inn på en organisasjon",
"orgAuthSignInToOrg": "Sign in to an organization",
"orgAuthSelectOrgTitle": "Organisasjonsinnlogging",
"orgAuthSelectOrgDescription": "Skriv inn organisasjons-ID-en din for å fortsette",
"orgAuthOrgIdPlaceholder": "din-organisasjon",
@@ -2231,6 +2266,8 @@
"deviceCodeInvalidFormat": "Kode må inneholde 9 tegn (f.eks A1AJ-N5JD)",
"deviceCodeInvalidOrExpired": "Ugyldig eller utløpt kode",
"deviceCodeVerifyFailed": "Klarte ikke å bekrefte enhetskoden",
"deviceCodeValidating": "Validating device code...",
"deviceCodeVerifying": "Verifying device authorization...",
"signedInAs": "Logget inn som",
"deviceCodeEnterPrompt": "Skriv inn koden som vises på enheten",
"continue": "Fortsett",
@@ -2243,7 +2280,7 @@
"deviceOrganizationsAccess": "Tilgang til alle organisasjoner din konto har tilgang til",
"deviceAuthorize": "Autoriser {applicationName}",
"deviceConnected": "Enhet tilkoblet!",
"deviceAuthorizedMessage": "Enhet er autorisert for tilgang til kontoen din.",
"deviceAuthorizedMessage": "Device is authorized to access your account. Please return to the client application.",
"pangolinCloud": "Pangolin Sky",
"viewDevices": "Vis enheter",
"viewDevicesDescription": "Administrer tilkoblede enheter",
@@ -2305,6 +2342,7 @@
"identifier": "Identifier",
"deviceLoginUseDifferentAccount": "Ikke du? Bruk en annen konto.",
"deviceLoginDeviceRequestingAccessToAccount": "En enhet ber om tilgang til denne kontoen.",
"loginSelectAuthenticationMethod": "Select an authentication method to continue.",
"noData": "Ingen data",
"machineClients": "Maskinklienter",
"install": "Installer",
@@ -2349,6 +2387,7 @@
"enterConfirmation": "Skriv inn bekreftelse",
"blueprintViewDetails": "Detaljer",
"defaultIdentityProvider": "Standard identitetsleverandør",
"defaultIdentityProviderDescription": "Når en standard identitetsleverandør er valgt, vil brukeren automatisk bli omdirigert til leverandøren for autentisering.",
"editInternalResourceDialogNetworkSettings": "Nettverksinnstillinger",
"editInternalResourceDialogAccessPolicy": "Tilgangsregler for tilgang",
"editInternalResourceDialogAddRoles": "Legg til roller",
@@ -2392,5 +2431,56 @@
"maintenanceScreenTitle": "Tjenesten er midlertidig utilgjengelig",
"maintenanceScreenMessage": "Vi opplever for øyeblikket tekniske problemer. Vennligst sjekk igjen snart.",
"maintenanceScreenEstimatedCompletion": "Estimert ferdigstillelse:",
"createInternalResourceDialogDestinationRequired": "Destinasjonen er nødvendig"
"createInternalResourceDialogDestinationRequired": "Destinasjonen er nødvendig",
"available": "Available",
"archived": "Archived",
"noArchivedDevices": "No archived devices found",
"deviceArchived": "Device archived",
"deviceArchivedDescription": "The device has been successfully archived.",
"errorArchivingDevice": "Error archiving device",
"failedToArchiveDevice": "Failed to archive device",
"deviceQuestionArchive": "Are you sure you want to archive this device?",
"deviceMessageArchive": "The device will be archived and removed from your active devices list.",
"deviceArchiveConfirm": "Archive Device",
"archiveDevice": "Archive Device",
"archive": "Archive",
"deviceUnarchived": "Device unarchived",
"deviceUnarchivedDescription": "The device has been successfully unarchived.",
"errorUnarchivingDevice": "Error unarchiving device",
"failedToUnarchiveDevice": "Failed to unarchive device",
"unarchive": "Unarchive",
"archiveClient": "Archive Client",
"archiveClientQuestion": "Are you sure you want to archive this client?",
"archiveClientMessage": "The client will be archived and removed from your active clients list.",
"archiveClientConfirm": "Archive Client",
"blockClient": "Block Client",
"blockClientQuestion": "Are you sure you want to block this client?",
"blockClientMessage": "The device will be forced to disconnect if currently connected. You can unblock the device later.",
"blockClientConfirm": "Block Client",
"active": "Active",
"usernameOrEmail": "Username or Email",
"selectYourOrganization": "Select your organization",
"signInTo": "Log in in to",
"signInWithPassword": "Continue with Password",
"noAuthMethodsAvailable": "No authentication methods available for this organization.",
"enterPassword": "Enter your password",
"enterMfaCode": "Enter the code from your authenticator app",
"securityKeyRequired": "Please use your security key to sign in.",
"needToUseAnotherAccount": "Need to use a different account?",
"loginLegalDisclaimer": "By clicking the buttons below, you acknowledge you have read, understand, and agree to the <termsOfService>Terms of Service</termsOfService> and <privacyPolicy>Privacy Policy</privacyPolicy>.",
"termsOfService": "Terms of Service",
"privacyPolicy": "Privacy Policy",
"userNotFoundWithUsername": "No user found with that username.",
"verify": "Verify",
"signIn": "Sign In",
"forgotPassword": "Forgot password?",
"orgSignInTip": "If you've logged in before, you can enter your username or email above to authenticate with your organization's identity provider instead. It's easier!",
"continueAnyway": "Continue anyway",
"dontShowAgain": "Don't show again",
"orgSignInNotice": "Did you know?",
"signupOrgNotice": "Trying to sign in?",
"signupOrgTip": "Are you trying to sign in through your organization's identity provider?",
"signupOrgLink": "Sign in or sign up with your organization instead",
"verifyEmailLogInWithDifferentAccount": "Use a Different Account",
"logIn": "Log In"
}

View File

@@ -257,6 +257,8 @@
"accessRolesSearch": "Rollen zoeken...",
"accessRolesAdd": "Rol toevoegen",
"accessRoleDelete": "Verwijder rol",
"accessApprovalsManage": "Manage Approvals",
"accessApprovalsDescription": "Manage approval requests in the organization",
"description": "Beschrijving",
"inviteTitle": "Open uitnodigingen",
"inviteDescription": "Beheer uitnodigingen voor andere gebruikers om deel te nemen aan de organisatie",
@@ -450,6 +452,18 @@
"selectDuration": "Selecteer duur",
"selectResource": "Selecteer Document",
"filterByResource": "Filter op pagina",
"selectApprovalState": "Select Approval State",
"filterByApprovalState": "Filter By Approval State",
"approvalListEmpty": "No approvals",
"approvalState": "Approval State",
"approve": "Approve",
"approved": "Approved",
"denied": "Denied",
"deniedApproval": "Denied Approval",
"all": "All",
"deny": "Deny",
"viewDetails": "View Details",
"requestingNewDeviceApproval": "requested a new device",
"resetFilters": "Filters resetten",
"totalBlocked": "Verzoeken geblokkeerd door Pangolin",
"totalRequests": "Totaal verzoeken",
@@ -729,16 +743,28 @@
"countries": "Landen",
"accessRoleCreate": "Rol aanmaken",
"accessRoleCreateDescription": "Maak een nieuwe rol aan om gebruikers te groeperen en hun rechten te beheren.",
"accessRoleEdit": "Edit Role",
"accessRoleEditDescription": "Edit role information.",
"accessRoleCreateSubmit": "Rol aanmaken",
"accessRoleCreated": "Rol aangemaakt",
"accessRoleCreatedDescription": "De rol is succesvol aangemaakt.",
"accessRoleErrorCreate": "Rol aanmaken mislukt",
"accessRoleErrorCreateDescription": "Fout opgetreden tijdens het aanmaken van de rol.",
"accessRoleUpdateSubmit": "Update Role",
"accessRoleUpdated": "Role updated",
"accessRoleUpdatedDescription": "The role has been successfully updated.",
"accessApprovalUpdated": "Approval processed",
"accessApprovalApprovedDescription": "Set Approval Request decision to approved.",
"accessApprovalDeniedDescription": "Set Approval Request decision to denied.",
"accessRoleErrorUpdate": "Failed to update role",
"accessRoleErrorUpdateDescription": "An error occurred while updating the role.",
"accessApprovalErrorUpdate": "Failed to process approval",
"accessApprovalErrorUpdateDescription": "An error occurred while processing the approval.",
"accessRoleErrorNewRequired": "Nieuwe rol is vereist",
"accessRoleErrorRemove": "Rol verwijderen mislukt",
"accessRoleErrorRemoveDescription": "Er is een fout opgetreden tijdens het verwijderen van de rol.",
"accessRoleName": "Rol naam",
"accessRoleQuestionRemove": "U staat op het punt de {name} rol te verwijderen. U kunt deze actie niet ongedaan maken.",
"accessRoleQuestionRemove": "You're about to delete the `{name}` role. You cannot undo this action.",
"accessRoleRemove": "Rol verwijderen",
"accessRoleRemoveDescription": "Verwijder een rol van de organisatie",
"accessRoleRemoveSubmit": "Rol verwijderen",
@@ -850,6 +876,7 @@
"orgPolicyConfig": "Toegang voor een organisatie configureren",
"idpUpdatedDescription": "Identity provider succesvol bijgewerkt",
"redirectUrl": "Omleidings URL",
"orgIdpRedirectUrls": "URL's omleiden",
"redirectUrlAbout": "Over omleidings-URL",
"redirectUrlAboutDescription": "Dit is de URL waarnaar gebruikers worden doorverwezen na verificatie. U moet deze URL configureren in de instellingen van de identiteitsprovider.",
"pangolinAuth": "Authenticatie - Pangolin",
@@ -873,7 +900,7 @@
"inviteAlready": "Het lijkt erop dat je bent uitgenodigd!",
"inviteAlreadyDescription": "Om de uitnodiging te accepteren, moet je inloggen of een account aanmaken.",
"signupQuestion": "Heeft u al een account?",
"login": "Inloggen",
"login": "Log In",
"resourceNotFound": "Bron niet gevonden",
"resourceNotFoundDescription": "De bron die u probeert te benaderen bestaat niet.",
"pincodeRequirementsLength": "Pincode moet precies 6 cijfers zijn",
@@ -953,13 +980,13 @@
"passwordExpiryDescription": "Deze organisatie vereist dat u om de {maxDays} dagen uw wachtwoord wijzigt.",
"changePasswordNow": "Wijzig wachtwoord nu",
"pincodeAuth": "Authenticatiecode",
"pincodeSubmit2": "Code indienen",
"pincodeSubmit2": "Submit code",
"passwordResetSubmit": "Opnieuw instellen aanvragen",
"passwordResetAlreadyHaveCode": "Code invoeren",
"passwordResetSmtpRequired": "Neem contact op met uw beheerder",
"passwordResetSmtpRequiredDescription": "Er is een wachtwoord reset code nodig om uw wachtwoord opnieuw in te stellen. Neem contact op met uw beheerder voor hulp.",
"passwordBack": "Terug naar wachtwoord",
"loginBack": "Ga terug naar login",
"loginBack": "Go back to main login page",
"signup": "Registreer nu",
"loginStart": "Log in om te beginnen",
"idpOidcTokenValidating": "Valideer OIDC-token",
@@ -1117,6 +1144,10 @@
"actionUpdateIdpOrg": "IDP-org bijwerken",
"actionCreateClient": "Client aanmaken",
"actionDeleteClient": "Verwijder klant",
"actionArchiveClient": "Archive Client",
"actionUnarchiveClient": "Unarchive Client",
"actionBlockClient": "Block Client",
"actionUnblockClient": "Unblock Client",
"actionUpdateClient": "Klant bijwerken",
"actionListClients": "Lijst klanten",
"actionGetClient": "Client ophalen",
@@ -1133,14 +1164,14 @@
"searchProgress": "Zoeken...",
"create": "Aanmaken",
"orgs": "Organisaties",
"loginError": "Er is een fout opgetreden tijdens het inloggen",
"loginRequiredForDevice": "Inloggen is vereist om je apparaat te verifiëren.",
"loginError": "An unexpected error occurred. Please try again.",
"loginRequiredForDevice": "Login is required for your device.",
"passwordForgot": "Wachtwoord vergeten?",
"otpAuth": "Tweestapsverificatie verificatie",
"otpAuthDescription": "Voer de code van je authenticator-app of een van je reservekopiecodes voor het eenmalig gebruik in.",
"otpAuthSubmit": "Code indienen",
"idpContinue": "Of ga verder met",
"otpAuthBack": "Terug naar inloggen",
"otpAuthBack": "Back to Password",
"navbar": "Navigatiemenu",
"navbarDescription": "Hoofd navigatie menu voor de applicatie",
"navbarDocsLink": "Documentatie",
@@ -1188,6 +1219,7 @@
"sidebarOverview": "Overzicht.",
"sidebarHome": "Startpagina",
"sidebarSites": "Werkruimtes",
"sidebarApprovals": "Approval Requests",
"sidebarResources": "Bronnen",
"sidebarProxyResources": "Openbaar",
"sidebarClientResources": "Privé",
@@ -1204,7 +1236,7 @@
"sidebarIdentityProviders": "Identiteit aanbieders",
"sidebarLicense": "Licentie",
"sidebarClients": "Clienten",
"sidebarUserDevices": "Gebruikers",
"sidebarUserDevices": "User Devices",
"sidebarMachineClients": "Machines",
"sidebarDomains": "Domeinen",
"sidebarGeneral": "Beheren",
@@ -1303,6 +1335,7 @@
"refreshError": "Het vernieuwen van gegevens is mislukt",
"verified": "Gecontroleerd",
"pending": "In afwachting",
"pendingApproval": "Pending Approval",
"sidebarBilling": "Facturering",
"billing": "Facturering",
"orgBillingDescription": "Beheer factureringsinformatie en abonnementen",
@@ -1419,7 +1452,7 @@
"securityKeyRemoveSuccess": "Beveiligingssleutel succesvol verwijderd",
"securityKeyRemoveError": "Fout bij verwijderen van beveiligingssleutel",
"securityKeyLoadError": "Fout bij laden van beveiligingssleutels",
"securityKeyLogin": "Doorgaan met beveiligingssleutel",
"securityKeyLogin": "Use Security Key",
"securityKeyAuthError": "Fout bij authenticatie met beveiligingssleutel",
"securityKeyRecommendation": "Overweeg om een andere beveiligingssleutel te registreren op een ander apparaat om ervoor te zorgen dat u niet buitengesloten raakt van uw account.",
"registering": "Registreren...",
@@ -1479,7 +1512,7 @@
"IAgreeToThe": "Ik ga akkoord met de",
"termsOfService": "servicevoorwaarden",
"and": "en",
"privacyPolicy": "privacybeleid"
"privacyPolicy": "privacy beleid"
},
"signUpMarketing": {
"keepMeInTheLoop": "Houd me op de hoogte met nieuws, updates en nieuwe functies per e-mail."
@@ -1546,6 +1579,8 @@
"IntervalSeconds": "Gezonde Interval",
"timeoutSeconds": "Timeout (sec)",
"timeIsInSeconds": "Tijd is in seconden",
"requireDeviceApproval": "Require Device Approvals",
"requireDeviceApprovalDescription": "Users with this role need their devices approved by an admin before they can access resources",
"retryAttempts": "Herhaal Pogingen",
"expectedResponseCodes": "Verwachte Reactiecodes",
"expectedResponseCodesDescription": "HTTP-statuscode die gezonde status aangeeft. Indien leeg wordt 200-300 als gezond beschouwd.",
@@ -1875,7 +1910,7 @@
"orgAuthChooseIdpDescription": "Kies uw identiteitsprovider om door te gaan",
"orgAuthNoIdpConfigured": "Deze organisatie heeft geen identiteitsproviders geconfigureerd. Je kunt in plaats daarvan inloggen met je Pangolin-identiteit.",
"orgAuthSignInWithPangolin": "Log in met Pangolin",
"orgAuthSignInToOrg": "Meld u aan bij een organisatie",
"orgAuthSignInToOrg": "Sign in to an organization",
"orgAuthSelectOrgTitle": "Organisatie Inloggen",
"orgAuthSelectOrgDescription": "Voer je organisatie-ID in om verder te gaan",
"orgAuthOrgIdPlaceholder": "jouw-organisatie",
@@ -2231,6 +2266,8 @@
"deviceCodeInvalidFormat": "Code moet 9 tekens bevatten (bijv. A1AJ-N5JD)",
"deviceCodeInvalidOrExpired": "Ongeldige of verlopen code",
"deviceCodeVerifyFailed": "Apparaatcode verifiëren mislukt",
"deviceCodeValidating": "Validating device code...",
"deviceCodeVerifying": "Verifying device authorization...",
"signedInAs": "Ingelogd als",
"deviceCodeEnterPrompt": "Voer de op het apparaat weergegeven code in",
"continue": "Doorgaan",
@@ -2243,7 +2280,7 @@
"deviceOrganizationsAccess": "Toegang tot alle organisaties waar uw account toegang tot heeft",
"deviceAuthorize": "Autoriseer {applicationName}",
"deviceConnected": "Apparaat verbonden!",
"deviceAuthorizedMessage": "Apparaat is gemachtigd om toegang te krijgen tot je account.",
"deviceAuthorizedMessage": "Device is authorized to access your account. Please return to the client application.",
"pangolinCloud": "Pangoline Cloud",
"viewDevices": "Bekijk apparaten",
"viewDevicesDescription": "Beheer uw aangesloten apparaten",
@@ -2305,6 +2342,7 @@
"identifier": "Identifier",
"deviceLoginUseDifferentAccount": "Niet u? Gebruik een ander account.",
"deviceLoginDeviceRequestingAccessToAccount": "Een apparaat vraagt om toegang tot dit account.",
"loginSelectAuthenticationMethod": "Select an authentication method to continue.",
"noData": "Geen gegevens",
"machineClients": "Machine Clienten",
"install": "Installeren",
@@ -2349,6 +2387,7 @@
"enterConfirmation": "Bevestiging invoeren",
"blueprintViewDetails": "Details",
"defaultIdentityProvider": "Standaard Identiteitsprovider",
"defaultIdentityProviderDescription": "Wanneer een standaard identity provider is geselecteerd, zal de gebruiker automatisch worden doorgestuurd naar de provider voor authenticatie.",
"editInternalResourceDialogNetworkSettings": "Netwerkinstellingen",
"editInternalResourceDialogAccessPolicy": "Toegangsbeleid",
"editInternalResourceDialogAddRoles": "Rollen toevoegen",
@@ -2392,5 +2431,56 @@
"maintenanceScreenTitle": "Dienst tijdelijk niet beschikbaar",
"maintenanceScreenMessage": "We hebben momenteel technische problemen. Probeer het later opnieuw.",
"maintenanceScreenEstimatedCompletion": "Geschatte voltooiing:",
"createInternalResourceDialogDestinationRequired": "Bestemming is vereist"
"createInternalResourceDialogDestinationRequired": "Bestemming is vereist",
"available": "Available",
"archived": "Archived",
"noArchivedDevices": "No archived devices found",
"deviceArchived": "Device archived",
"deviceArchivedDescription": "The device has been successfully archived.",
"errorArchivingDevice": "Error archiving device",
"failedToArchiveDevice": "Failed to archive device",
"deviceQuestionArchive": "Are you sure you want to archive this device?",
"deviceMessageArchive": "The device will be archived and removed from your active devices list.",
"deviceArchiveConfirm": "Archive Device",
"archiveDevice": "Archive Device",
"archive": "Archive",
"deviceUnarchived": "Device unarchived",
"deviceUnarchivedDescription": "The device has been successfully unarchived.",
"errorUnarchivingDevice": "Error unarchiving device",
"failedToUnarchiveDevice": "Failed to unarchive device",
"unarchive": "Unarchive",
"archiveClient": "Archive Client",
"archiveClientQuestion": "Are you sure you want to archive this client?",
"archiveClientMessage": "The client will be archived and removed from your active clients list.",
"archiveClientConfirm": "Archive Client",
"blockClient": "Block Client",
"blockClientQuestion": "Are you sure you want to block this client?",
"blockClientMessage": "The device will be forced to disconnect if currently connected. You can unblock the device later.",
"blockClientConfirm": "Block Client",
"active": "Active",
"usernameOrEmail": "Username or Email",
"selectYourOrganization": "Select your organization",
"signInTo": "Log in in to",
"signInWithPassword": "Continue with Password",
"noAuthMethodsAvailable": "No authentication methods available for this organization.",
"enterPassword": "Enter your password",
"enterMfaCode": "Enter the code from your authenticator app",
"securityKeyRequired": "Please use your security key to sign in.",
"needToUseAnotherAccount": "Need to use a different account?",
"loginLegalDisclaimer": "By clicking the buttons below, you acknowledge you have read, understand, and agree to the <termsOfService>Terms of Service</termsOfService> and <privacyPolicy>Privacy Policy</privacyPolicy>.",
"termsOfService": "Terms of Service",
"privacyPolicy": "Privacy Policy",
"userNotFoundWithUsername": "No user found with that username.",
"verify": "Verify",
"signIn": "Sign In",
"forgotPassword": "Forgot password?",
"orgSignInTip": "If you've logged in before, you can enter your username or email above to authenticate with your organization's identity provider instead. It's easier!",
"continueAnyway": "Continue anyway",
"dontShowAgain": "Don't show again",
"orgSignInNotice": "Did you know?",
"signupOrgNotice": "Trying to sign in?",
"signupOrgTip": "Are you trying to sign in through your organization's identity provider?",
"signupOrgLink": "Sign in or sign up with your organization instead",
"verifyEmailLogInWithDifferentAccount": "Use a Different Account",
"logIn": "Log In"
}

View File

@@ -257,6 +257,8 @@
"accessRolesSearch": "Szukaj ról...",
"accessRolesAdd": "Dodaj rolę",
"accessRoleDelete": "Usuń rolę",
"accessApprovalsManage": "Manage Approvals",
"accessApprovalsDescription": "Manage approval requests in the organization",
"description": "Opis",
"inviteTitle": "Otwórz zaproszenia",
"inviteDescription": "Zarządzaj zaproszeniami dla innych użytkowników do dołączenia do organizacji",
@@ -450,6 +452,18 @@
"selectDuration": "Wybierz okres",
"selectResource": "Wybierz zasób",
"filterByResource": "Filtruj według zasobów",
"selectApprovalState": "Select Approval State",
"filterByApprovalState": "Filter By Approval State",
"approvalListEmpty": "No approvals",
"approvalState": "Approval State",
"approve": "Approve",
"approved": "Approved",
"denied": "Denied",
"deniedApproval": "Denied Approval",
"all": "All",
"deny": "Deny",
"viewDetails": "View Details",
"requestingNewDeviceApproval": "requested a new device",
"resetFilters": "Resetuj filtry",
"totalBlocked": "Żądania zablokowane przez Pangolina",
"totalRequests": "Wszystkich Żądań",
@@ -729,16 +743,28 @@
"countries": "Kraje",
"accessRoleCreate": "Utwórz rolę",
"accessRoleCreateDescription": "Utwórz nową rolę aby zgrupować użytkowników i zarządzać ich uprawnieniami.",
"accessRoleEdit": "Edit Role",
"accessRoleEditDescription": "Edit role information.",
"accessRoleCreateSubmit": "Utwórz rolę",
"accessRoleCreated": "Rola utworzona",
"accessRoleCreatedDescription": "Rola została pomyślnie utworzona.",
"accessRoleErrorCreate": "Nie udało się utworzyć roli",
"accessRoleErrorCreateDescription": "Wystąpił błąd podczas tworzenia roli.",
"accessRoleUpdateSubmit": "Update Role",
"accessRoleUpdated": "Role updated",
"accessRoleUpdatedDescription": "The role has been successfully updated.",
"accessApprovalUpdated": "Approval processed",
"accessApprovalApprovedDescription": "Set Approval Request decision to approved.",
"accessApprovalDeniedDescription": "Set Approval Request decision to denied.",
"accessRoleErrorUpdate": "Failed to update role",
"accessRoleErrorUpdateDescription": "An error occurred while updating the role.",
"accessApprovalErrorUpdate": "Failed to process approval",
"accessApprovalErrorUpdateDescription": "An error occurred while processing the approval.",
"accessRoleErrorNewRequired": "Nowa rola jest wymagana",
"accessRoleErrorRemove": "Nie udało się usunąć roli",
"accessRoleErrorRemoveDescription": "Wystąpił błąd podczas usuwania roli.",
"accessRoleName": "Nazwa roli",
"accessRoleQuestionRemove": "Zamierzasz usunąć rolę {name}. Tej akcji nie można cofnąć.",
"accessRoleQuestionRemove": "You're about to delete the `{name}` role. You cannot undo this action.",
"accessRoleRemove": "Usuń rolę",
"accessRoleRemoveDescription": "Usuń rolę z organizacji",
"accessRoleRemoveSubmit": "Usuń rolę",
@@ -850,6 +876,7 @@
"orgPolicyConfig": "Skonfiguruj dostęp dla organizacji",
"idpUpdatedDescription": "Dostawca tożsamości został pomyślnie zaktualizowany",
"redirectUrl": "URL przekierowania",
"orgIdpRedirectUrls": "Przekieruj adresy URL",
"redirectUrlAbout": "O URL przekierowania",
"redirectUrlAboutDescription": "Jest to adres URL, na który użytkownicy zostaną przekierowani po uwierzytelnieniu. Musisz skonfigurować ten adres URL w ustawieniach dostawcy tożsamości.",
"pangolinAuth": "Autoryzacja - Pangolin",
@@ -873,7 +900,7 @@
"inviteAlready": "Wygląda na to, że zostałeś już zaproszony!",
"inviteAlreadyDescription": "Aby zaakceptować zaproszenie, musisz się zalogować lub utworzyć konto.",
"signupQuestion": "Masz już konto?",
"login": "Zaloguj się",
"login": "Log In",
"resourceNotFound": "Nie znaleziono zasobu",
"resourceNotFoundDescription": "Zasób, do którego próbujesz uzyskać dostęp, nie istnieje.",
"pincodeRequirementsLength": "PIN musi składać się dokładnie z 6 cyfr",
@@ -953,13 +980,13 @@
"passwordExpiryDescription": "Organizacja wymaga zmiany hasła co {maxDays} dni.",
"changePasswordNow": "Zmień hasło teraz",
"pincodeAuth": "Kod uwierzytelniający",
"pincodeSubmit2": "Wyślij kod",
"pincodeSubmit2": "Submit code",
"passwordResetSubmit": "Zażądaj resetowania",
"passwordResetAlreadyHaveCode": "Wprowadź kod",
"passwordResetSmtpRequired": "Skontaktuj się z administratorem",
"passwordResetSmtpRequiredDescription": "Aby zresetować hasło, wymagany jest kod resetowania hasła. Skontaktuj się z administratorem.",
"passwordBack": "Powrót do hasła",
"loginBack": "Wróć do logowania",
"loginBack": "Go back to main login page",
"signup": "Zarejestruj się",
"loginStart": "Zaloguj się, aby rozpocząć",
"idpOidcTokenValidating": "Walidacja tokena OIDC",
@@ -1117,6 +1144,10 @@
"actionUpdateIdpOrg": "Aktualizuj organizację IDP",
"actionCreateClient": "Utwórz klienta",
"actionDeleteClient": "Usuń klienta",
"actionArchiveClient": "Archive Client",
"actionUnarchiveClient": "Unarchive Client",
"actionBlockClient": "Block Client",
"actionUnblockClient": "Unblock Client",
"actionUpdateClient": "Aktualizuj klienta",
"actionListClients": "Lista klientów",
"actionGetClient": "Pobierz klienta",
@@ -1133,14 +1164,14 @@
"searchProgress": "Szukaj...",
"create": "Utwórz",
"orgs": "Organizacje",
"loginError": "Wystąpił błąd podczas logowania",
"loginRequiredForDevice": "Logowanie jest wymagane do uwierzytelnienia urządzenia.",
"loginError": "An unexpected error occurred. Please try again.",
"loginRequiredForDevice": "Login is required for your device.",
"passwordForgot": "Zapomniałeś hasła?",
"otpAuth": "Uwierzytelnianie dwuskładnikowe",
"otpAuthDescription": "Wprowadź kod z aplikacji uwierzytelniającej lub jeden z jednorazowych kodów zapasowych.",
"otpAuthSubmit": "Wyślij kod",
"idpContinue": "Lub kontynuuj z",
"otpAuthBack": "Powrót do logowania",
"otpAuthBack": "Back to Password",
"navbar": "Menu nawigacyjne",
"navbarDescription": "Główne menu nawigacyjne aplikacji",
"navbarDocsLink": "Dokumentacja",
@@ -1188,6 +1219,7 @@
"sidebarOverview": "Przegląd",
"sidebarHome": "Strona główna",
"sidebarSites": "Witryny",
"sidebarApprovals": "Approval Requests",
"sidebarResources": "Zasoby",
"sidebarProxyResources": "Publiczne",
"sidebarClientResources": "Prywatny",
@@ -1204,7 +1236,7 @@
"sidebarIdentityProviders": "Dostawcy tożsamości",
"sidebarLicense": "Licencja",
"sidebarClients": "Klienty",
"sidebarUserDevices": "Użytkownicy",
"sidebarUserDevices": "User Devices",
"sidebarMachineClients": "Maszyny",
"sidebarDomains": "Domeny",
"sidebarGeneral": "Zarządzaj",
@@ -1303,6 +1335,7 @@
"refreshError": "Nie udało się odświeżyć danych",
"verified": "Zatwierdzony",
"pending": "Oczekuje",
"pendingApproval": "Pending Approval",
"sidebarBilling": "Fakturowanie",
"billing": "Fakturowanie",
"orgBillingDescription": "Zarządzaj informacjami rozliczeniowymi i subskrypcjami",
@@ -1419,7 +1452,7 @@
"securityKeyRemoveSuccess": "Klucz bezpieczeństwa został pomyślnie usunięty",
"securityKeyRemoveError": "Błąd podczas usuwania klucza bezpieczeństwa",
"securityKeyLoadError": "Błąd podczas ładowania kluczy bezpieczeństwa",
"securityKeyLogin": "Zaloguj się kluczem bezpieczeństwa",
"securityKeyLogin": "Use Security Key",
"securityKeyAuthError": "Błąd podczas uwierzytelniania kluczem bezpieczeństwa",
"securityKeyRecommendation": "Rozważ zarejestrowanie innego klucza bezpieczeństwa na innym urządzeniu, aby upewnić się, że nie zostaniesz zablokowany z dostępu do swojego konta.",
"registering": "Rejestracja...",
@@ -1479,7 +1512,7 @@
"IAgreeToThe": "Zgadzam się z",
"termsOfService": "warunkami usługi",
"and": "oraz",
"privacyPolicy": "polityką prywatności"
"privacyPolicy": "polityka prywatności."
},
"signUpMarketing": {
"keepMeInTheLoop": "Zachowaj mnie w pętli z wiadomościami, aktualizacjami i nowymi funkcjami przez e-mail."
@@ -1546,6 +1579,8 @@
"IntervalSeconds": "Interwał Zdrowy",
"timeoutSeconds": "Limit czasu (sek)",
"timeIsInSeconds": "Czas w sekundach",
"requireDeviceApproval": "Require Device Approvals",
"requireDeviceApprovalDescription": "Users with this role need their devices approved by an admin before they can access resources",
"retryAttempts": "Próby Ponowienia",
"expectedResponseCodes": "Oczekiwane Kody Odpowiedzi",
"expectedResponseCodesDescription": "Kod statusu HTTP, który wskazuje zdrowy status. Jeśli pozostanie pusty, uznaje się 200-300 za zdrowy.",
@@ -1875,7 +1910,7 @@
"orgAuthChooseIdpDescription": "Wybierz swojego dostawcę tożsamości, aby kontynuować",
"orgAuthNoIdpConfigured": "Ta organizacja nie ma skonfigurowanych żadnych dostawców tożsamości. Zamiast tego możesz zalogować się za pomocą swojej tożsamości Pangolin.",
"orgAuthSignInWithPangolin": "Zaloguj się używając Pangolin",
"orgAuthSignInToOrg": "Zaloguj się do organizacji",
"orgAuthSignInToOrg": "Sign in to an organization",
"orgAuthSelectOrgTitle": "Logowanie do organizacji",
"orgAuthSelectOrgDescription": "Wprowadź identyfikator organizacji, aby kontynuować",
"orgAuthOrgIdPlaceholder": "twoja-organizacja",
@@ -2231,6 +2266,8 @@
"deviceCodeInvalidFormat": "Kod musi mieć 9 znaków (np. A1AJ-N5JD)",
"deviceCodeInvalidOrExpired": "Nieprawidłowy lub wygasły kod",
"deviceCodeVerifyFailed": "Nie udało się zweryfikować kodu urządzenia",
"deviceCodeValidating": "Validating device code...",
"deviceCodeVerifying": "Verifying device authorization...",
"signedInAs": "Zalogowany jako",
"deviceCodeEnterPrompt": "Wprowadź kod wyświetlany na urządzeniu",
"continue": "Kontynuuj",
@@ -2243,7 +2280,7 @@
"deviceOrganizationsAccess": "Dostęp do wszystkich organizacji, do których Twoje konto ma dostęp",
"deviceAuthorize": "Autoryzuj {applicationName}",
"deviceConnected": "Urządzenie podłączone!",
"deviceAuthorizedMessage": "Urządzenie jest upoważnione do dostępu do Twojego konta.",
"deviceAuthorizedMessage": "Device is authorized to access your account. Please return to the client application.",
"pangolinCloud": "Chmura Pangolin",
"viewDevices": "Zobacz urządzenia",
"viewDevicesDescription": "Zarządzaj podłączonymi urządzeniami",
@@ -2305,6 +2342,7 @@
"identifier": "Identifier",
"deviceLoginUseDifferentAccount": "Nie ty? Użyj innego konta.",
"deviceLoginDeviceRequestingAccessToAccount": "Urządzenie żąda dostępu do tego konta.",
"loginSelectAuthenticationMethod": "Select an authentication method to continue.",
"noData": "Brak danych",
"machineClients": "Klienci maszyn",
"install": "Zainstaluj",
@@ -2349,6 +2387,7 @@
"enterConfirmation": "Wprowadź potwierdzenie",
"blueprintViewDetails": "Szczegóły",
"defaultIdentityProvider": "Domyślny dostawca tożsamości",
"defaultIdentityProviderDescription": "Gdy zostanie wybrany domyślny dostawca tożsamości, użytkownik zostanie automatycznie przekierowany do dostawcy w celu uwierzytelnienia.",
"editInternalResourceDialogNetworkSettings": "Ustawienia sieci",
"editInternalResourceDialogAccessPolicy": "Polityka dostępowa",
"editInternalResourceDialogAddRoles": "Dodaj role",
@@ -2392,5 +2431,56 @@
"maintenanceScreenTitle": "Usługa chwilowo niedostępna",
"maintenanceScreenMessage": "Obecnie doświadczamy problemów technicznych. Proszę sprawdzić ponownie wkrótce.",
"maintenanceScreenEstimatedCompletion": "Szacowane zakończenie:",
"createInternalResourceDialogDestinationRequired": "Miejsce docelowe jest wymagane"
"createInternalResourceDialogDestinationRequired": "Miejsce docelowe jest wymagane",
"available": "Available",
"archived": "Archived",
"noArchivedDevices": "No archived devices found",
"deviceArchived": "Device archived",
"deviceArchivedDescription": "The device has been successfully archived.",
"errorArchivingDevice": "Error archiving device",
"failedToArchiveDevice": "Failed to archive device",
"deviceQuestionArchive": "Are you sure you want to archive this device?",
"deviceMessageArchive": "The device will be archived and removed from your active devices list.",
"deviceArchiveConfirm": "Archive Device",
"archiveDevice": "Archive Device",
"archive": "Archive",
"deviceUnarchived": "Device unarchived",
"deviceUnarchivedDescription": "The device has been successfully unarchived.",
"errorUnarchivingDevice": "Error unarchiving device",
"failedToUnarchiveDevice": "Failed to unarchive device",
"unarchive": "Unarchive",
"archiveClient": "Archive Client",
"archiveClientQuestion": "Are you sure you want to archive this client?",
"archiveClientMessage": "The client will be archived and removed from your active clients list.",
"archiveClientConfirm": "Archive Client",
"blockClient": "Block Client",
"blockClientQuestion": "Are you sure you want to block this client?",
"blockClientMessage": "The device will be forced to disconnect if currently connected. You can unblock the device later.",
"blockClientConfirm": "Block Client",
"active": "Active",
"usernameOrEmail": "Username or Email",
"selectYourOrganization": "Select your organization",
"signInTo": "Log in in to",
"signInWithPassword": "Continue with Password",
"noAuthMethodsAvailable": "No authentication methods available for this organization.",
"enterPassword": "Enter your password",
"enterMfaCode": "Enter the code from your authenticator app",
"securityKeyRequired": "Please use your security key to sign in.",
"needToUseAnotherAccount": "Need to use a different account?",
"loginLegalDisclaimer": "By clicking the buttons below, you acknowledge you have read, understand, and agree to the <termsOfService>Terms of Service</termsOfService> and <privacyPolicy>Privacy Policy</privacyPolicy>.",
"termsOfService": "Terms of Service",
"privacyPolicy": "Privacy Policy",
"userNotFoundWithUsername": "No user found with that username.",
"verify": "Verify",
"signIn": "Sign In",
"forgotPassword": "Forgot password?",
"orgSignInTip": "If you've logged in before, you can enter your username or email above to authenticate with your organization's identity provider instead. It's easier!",
"continueAnyway": "Continue anyway",
"dontShowAgain": "Don't show again",
"orgSignInNotice": "Did you know?",
"signupOrgNotice": "Trying to sign in?",
"signupOrgTip": "Are you trying to sign in through your organization's identity provider?",
"signupOrgLink": "Sign in or sign up with your organization instead",
"verifyEmailLogInWithDifferentAccount": "Use a Different Account",
"logIn": "Log In"
}

View File

@@ -257,6 +257,8 @@
"accessRolesSearch": "Pesquisar funções...",
"accessRolesAdd": "Adicionar função",
"accessRoleDelete": "Excluir Papel",
"accessApprovalsManage": "Manage Approvals",
"accessApprovalsDescription": "Manage approval requests in the organization",
"description": "Descrição:",
"inviteTitle": "Convites Abertos",
"inviteDescription": "Gerenciar convites para outros usuários participarem da organização",
@@ -450,6 +452,18 @@
"selectDuration": "Selecionar duração",
"selectResource": "Selecionar Recurso",
"filterByResource": "Filtrar por Recurso",
"selectApprovalState": "Select Approval State",
"filterByApprovalState": "Filter By Approval State",
"approvalListEmpty": "No approvals",
"approvalState": "Approval State",
"approve": "Approve",
"approved": "Approved",
"denied": "Denied",
"deniedApproval": "Denied Approval",
"all": "All",
"deny": "Deny",
"viewDetails": "View Details",
"requestingNewDeviceApproval": "requested a new device",
"resetFilters": "Redefinir filtros",
"totalBlocked": "Solicitações bloqueadas pelo Pangolin",
"totalRequests": "Total de pedidos",
@@ -729,16 +743,28 @@
"countries": "Países",
"accessRoleCreate": "Criar Função",
"accessRoleCreateDescription": "Crie uma nova função para agrupar utilizadores e gerir suas permissões.",
"accessRoleEdit": "Edit Role",
"accessRoleEditDescription": "Edit role information.",
"accessRoleCreateSubmit": "Criar Função",
"accessRoleCreated": "Função criada",
"accessRoleCreatedDescription": "A função foi criada com sucesso.",
"accessRoleErrorCreate": "Falha ao criar função",
"accessRoleErrorCreateDescription": "Ocorreu um erro ao criar a função.",
"accessRoleUpdateSubmit": "Update Role",
"accessRoleUpdated": "Role updated",
"accessRoleUpdatedDescription": "The role has been successfully updated.",
"accessApprovalUpdated": "Approval processed",
"accessApprovalApprovedDescription": "Set Approval Request decision to approved.",
"accessApprovalDeniedDescription": "Set Approval Request decision to denied.",
"accessRoleErrorUpdate": "Failed to update role",
"accessRoleErrorUpdateDescription": "An error occurred while updating the role.",
"accessApprovalErrorUpdate": "Failed to process approval",
"accessApprovalErrorUpdateDescription": "An error occurred while processing the approval.",
"accessRoleErrorNewRequired": "Nova função é necessária",
"accessRoleErrorRemove": "Falha ao remover função",
"accessRoleErrorRemoveDescription": "Ocorreu um erro ao remover a função.",
"accessRoleName": "Nome da Função",
"accessRoleQuestionRemove": "Você está prestes a apagar a função {name}. Você não pode desfazer esta ação.",
"accessRoleQuestionRemove": "You're about to delete the `{name}` role. You cannot undo this action.",
"accessRoleRemove": "Remover Função",
"accessRoleRemoveDescription": "Remover uma função da organização",
"accessRoleRemoveSubmit": "Remover Função",
@@ -850,6 +876,7 @@
"orgPolicyConfig": "Configurar acesso para uma organização",
"idpUpdatedDescription": "Provedor de identidade atualizado com sucesso",
"redirectUrl": "URL de Redirecionamento",
"orgIdpRedirectUrls": "Redirecionar URLs",
"redirectUrlAbout": "Sobre o URL de Redirecionamento",
"redirectUrlAboutDescription": "Essa é a URL para a qual os usuários serão redirecionados após a autenticação. Você precisa configurar esta URL nas configurações do provedor de identidade.",
"pangolinAuth": "Autenticação - Pangolin",
@@ -873,7 +900,7 @@
"inviteAlready": "Parece que já foi convidado!",
"inviteAlreadyDescription": "Para aceitar o convite, deve iniciar sessão ou criar uma conta.",
"signupQuestion": "Já tem uma conta?",
"login": "Iniciar sessão",
"login": "Log In",
"resourceNotFound": "Recurso Não Encontrado",
"resourceNotFoundDescription": "O recurso que está a tentar aceder não existe.",
"pincodeRequirementsLength": "O PIN deve ter exatamente 6 dígitos",
@@ -953,13 +980,13 @@
"passwordExpiryDescription": "Esta organização exige que você altere sua senha a cada {maxDays} dias.",
"changePasswordNow": "Alterar a senha agora",
"pincodeAuth": "Código do Autenticador",
"pincodeSubmit2": "Submeter Código",
"pincodeSubmit2": "Submit code",
"passwordResetSubmit": "Solicitar Redefinição",
"passwordResetAlreadyHaveCode": "Inserir Código",
"passwordResetSmtpRequired": "Por favor, contate o administrador",
"passwordResetSmtpRequiredDescription": "É necessário um código de redefinição de senha para redefinir sua senha. Por favor, contate o administrador para assistência.",
"passwordBack": "Voltar à Palavra-passe",
"loginBack": "Voltar ao início de sessão",
"loginBack": "Go back to main login page",
"signup": "Registar",
"loginStart": "Inicie sessão para começar",
"idpOidcTokenValidating": "A validar token OIDC",
@@ -1117,6 +1144,10 @@
"actionUpdateIdpOrg": "Atualizar Organização IDP",
"actionCreateClient": "Criar Cliente",
"actionDeleteClient": "Excluir Cliente",
"actionArchiveClient": "Archive Client",
"actionUnarchiveClient": "Unarchive Client",
"actionBlockClient": "Block Client",
"actionUnblockClient": "Unblock Client",
"actionUpdateClient": "Atualizar Cliente",
"actionListClients": "Listar Clientes",
"actionGetClient": "Obter Cliente",
@@ -1133,14 +1164,14 @@
"searchProgress": "Pesquisar...",
"create": "Criar",
"orgs": "Organizações",
"loginError": "Ocorreu um erro ao iniciar sessão",
"loginRequiredForDevice": "É necessário entrar para autenticar seu dispositivo.",
"loginError": "An unexpected error occurred. Please try again.",
"loginRequiredForDevice": "Login is required for your device.",
"passwordForgot": "Esqueceu a sua palavra-passe?",
"otpAuth": "Autenticação de Dois Fatores",
"otpAuthDescription": "Insira o código da sua aplicação de autenticação ou um dos seus códigos de backup de uso único.",
"otpAuthSubmit": "Submeter Código",
"idpContinue": "Ou continuar com",
"otpAuthBack": "Voltar ao Início de Sessão",
"otpAuthBack": "Back to Password",
"navbar": "Menu de Navegação",
"navbarDescription": "Menu de navegação principal da aplicação",
"navbarDocsLink": "Documentação",
@@ -1188,6 +1219,7 @@
"sidebarOverview": "Geral",
"sidebarHome": "Residencial",
"sidebarSites": "sites",
"sidebarApprovals": "Approval Requests",
"sidebarResources": "Recursos",
"sidebarProxyResources": "Público",
"sidebarClientResources": "Privado",
@@ -1204,7 +1236,7 @@
"sidebarIdentityProviders": "Provedores de identidade",
"sidebarLicense": "Tipo:",
"sidebarClients": "Clientes",
"sidebarUserDevices": "Utilizadores",
"sidebarUserDevices": "User Devices",
"sidebarMachineClients": "Máquinas",
"sidebarDomains": "Domínios",
"sidebarGeneral": "Gerir",
@@ -1303,6 +1335,7 @@
"refreshError": "Falha ao atualizar dados",
"verified": "Verificado",
"pending": "Pendente",
"pendingApproval": "Pending Approval",
"sidebarBilling": "Faturamento",
"billing": "Faturamento",
"orgBillingDescription": "Gerenciar informações e assinaturas de cobrança",
@@ -1419,7 +1452,7 @@
"securityKeyRemoveSuccess": "Chave de segurança removida com sucesso",
"securityKeyRemoveError": "Erro ao remover chave de segurança",
"securityKeyLoadError": "Erro ao carregar chaves de segurança",
"securityKeyLogin": "Continuar com a chave de segurança",
"securityKeyLogin": "Use Security Key",
"securityKeyAuthError": "Erro ao autenticar com chave de segurança",
"securityKeyRecommendation": "Considere registrar outra chave de segurança em um dispositivo diferente para garantir que você não fique bloqueado da sua conta.",
"registering": "Registrando...",
@@ -1479,7 +1512,7 @@
"IAgreeToThe": "Concordo com",
"termsOfService": "os termos de serviço",
"and": "e",
"privacyPolicy": "política de privacidade"
"privacyPolicy": "política de privacidade."
},
"signUpMarketing": {
"keepMeInTheLoop": "Mantenha-me à disposição com notícias, atualizações e novos recursos por e-mail."
@@ -1546,6 +1579,8 @@
"IntervalSeconds": "Intervalo Saudável",
"timeoutSeconds": "Tempo limite (seg)",
"timeIsInSeconds": "O tempo está em segundos",
"requireDeviceApproval": "Require Device Approvals",
"requireDeviceApprovalDescription": "Users with this role need their devices approved by an admin before they can access resources",
"retryAttempts": "Tentativas de Repetição",
"expectedResponseCodes": "Códigos de Resposta Esperados",
"expectedResponseCodesDescription": "Código de status HTTP que indica estado saudável. Se deixado em branco, 200-300 é considerado saudável.",
@@ -1875,7 +1910,7 @@
"orgAuthChooseIdpDescription": "Escolha o seu provedor de identidade para continuar",
"orgAuthNoIdpConfigured": "Esta organização não tem nenhum provedor de identidade configurado. Você pode entrar com a identidade do seu Pangolin.",
"orgAuthSignInWithPangolin": "Entrar com o Pangolin",
"orgAuthSignInToOrg": "Entrar em uma organização",
"orgAuthSignInToOrg": "Sign in to an organization",
"orgAuthSelectOrgTitle": "Entrada da Organização",
"orgAuthSelectOrgDescription": "Digite seu ID da organização para continuar",
"orgAuthOrgIdPlaceholder": "sua-organização",
@@ -2231,6 +2266,8 @@
"deviceCodeInvalidFormat": "O código deve ter 9 caracteres (ex.: A1AJ-N5JD)",
"deviceCodeInvalidOrExpired": "Código inválido ou expirado",
"deviceCodeVerifyFailed": "Falha ao verificar o código do dispositivo",
"deviceCodeValidating": "Validating device code...",
"deviceCodeVerifying": "Verifying device authorization...",
"signedInAs": "Sessão iniciada como",
"deviceCodeEnterPrompt": "Digite o código exibido no dispositivo",
"continue": "Continuar",
@@ -2243,7 +2280,7 @@
"deviceOrganizationsAccess": "Acesso a todas as organizações que sua conta tem acesso a",
"deviceAuthorize": "Autorizar {applicationName}",
"deviceConnected": "Dispositivo Conectado!",
"deviceAuthorizedMessage": "O dispositivo está autorizado a acessar sua conta.",
"deviceAuthorizedMessage": "Device is authorized to access your account. Please return to the client application.",
"pangolinCloud": "Nuvem do Pangolin",
"viewDevices": "Ver Dispositivos",
"viewDevicesDescription": "Gerencie seus dispositivos conectados",
@@ -2305,6 +2342,7 @@
"identifier": "Identifier",
"deviceLoginUseDifferentAccount": "Não é você? Use uma conta diferente.",
"deviceLoginDeviceRequestingAccessToAccount": "Um dispositivo está solicitando acesso a essa conta.",
"loginSelectAuthenticationMethod": "Select an authentication method to continue.",
"noData": "Nenhum dado encontrado",
"machineClients": "Clientes de máquina",
"install": "Instale",
@@ -2349,6 +2387,7 @@
"enterConfirmation": "Inserir confirmação",
"blueprintViewDetails": "Detalhes",
"defaultIdentityProvider": "Provedor de Identidade Padrão",
"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",
"editInternalResourceDialogAccessPolicy": "Política de Acesso",
"editInternalResourceDialogAddRoles": "Adicionar Funções",
@@ -2392,5 +2431,56 @@
"maintenanceScreenTitle": "Serviço Temporariamente Indisponível",
"maintenanceScreenMessage": "Estamos enfrentando dificuldades técnicas no momento. Por favor, volte em breve.",
"maintenanceScreenEstimatedCompletion": "Conclusão Estimada:",
"createInternalResourceDialogDestinationRequired": "Destino é obrigatório"
"createInternalResourceDialogDestinationRequired": "Destino é obrigatório",
"available": "Available",
"archived": "Archived",
"noArchivedDevices": "No archived devices found",
"deviceArchived": "Device archived",
"deviceArchivedDescription": "The device has been successfully archived.",
"errorArchivingDevice": "Error archiving device",
"failedToArchiveDevice": "Failed to archive device",
"deviceQuestionArchive": "Are you sure you want to archive this device?",
"deviceMessageArchive": "The device will be archived and removed from your active devices list.",
"deviceArchiveConfirm": "Archive Device",
"archiveDevice": "Archive Device",
"archive": "Archive",
"deviceUnarchived": "Device unarchived",
"deviceUnarchivedDescription": "The device has been successfully unarchived.",
"errorUnarchivingDevice": "Error unarchiving device",
"failedToUnarchiveDevice": "Failed to unarchive device",
"unarchive": "Unarchive",
"archiveClient": "Archive Client",
"archiveClientQuestion": "Are you sure you want to archive this client?",
"archiveClientMessage": "The client will be archived and removed from your active clients list.",
"archiveClientConfirm": "Archive Client",
"blockClient": "Block Client",
"blockClientQuestion": "Are you sure you want to block this client?",
"blockClientMessage": "The device will be forced to disconnect if currently connected. You can unblock the device later.",
"blockClientConfirm": "Block Client",
"active": "Active",
"usernameOrEmail": "Username or Email",
"selectYourOrganization": "Select your organization",
"signInTo": "Log in in to",
"signInWithPassword": "Continue with Password",
"noAuthMethodsAvailable": "No authentication methods available for this organization.",
"enterPassword": "Enter your password",
"enterMfaCode": "Enter the code from your authenticator app",
"securityKeyRequired": "Please use your security key to sign in.",
"needToUseAnotherAccount": "Need to use a different account?",
"loginLegalDisclaimer": "By clicking the buttons below, you acknowledge you have read, understand, and agree to the <termsOfService>Terms of Service</termsOfService> and <privacyPolicy>Privacy Policy</privacyPolicy>.",
"termsOfService": "Terms of Service",
"privacyPolicy": "Privacy Policy",
"userNotFoundWithUsername": "No user found with that username.",
"verify": "Verify",
"signIn": "Sign In",
"forgotPassword": "Forgot password?",
"orgSignInTip": "If you've logged in before, you can enter your username or email above to authenticate with your organization's identity provider instead. It's easier!",
"continueAnyway": "Continue anyway",
"dontShowAgain": "Don't show again",
"orgSignInNotice": "Did you know?",
"signupOrgNotice": "Trying to sign in?",
"signupOrgTip": "Are you trying to sign in through your organization's identity provider?",
"signupOrgLink": "Sign in or sign up with your organization instead",
"verifyEmailLogInWithDifferentAccount": "Use a Different Account",
"logIn": "Log In"
}

View File

@@ -257,6 +257,8 @@
"accessRolesSearch": "Поиск ролей...",
"accessRolesAdd": "Добавить роль",
"accessRoleDelete": "Удалить роль",
"accessApprovalsManage": "Manage Approvals",
"accessApprovalsDescription": "Manage approval requests in the organization",
"description": "Описание",
"inviteTitle": "Открытые приглашения",
"inviteDescription": "Управление приглашениями для присоединения других пользователей к организации",
@@ -450,6 +452,18 @@
"selectDuration": "Укажите срок действия",
"selectResource": "Выберите ресурс",
"filterByResource": "Фильтровать по ресурсам",
"selectApprovalState": "Select Approval State",
"filterByApprovalState": "Filter By Approval State",
"approvalListEmpty": "No approvals",
"approvalState": "Approval State",
"approve": "Approve",
"approved": "Approved",
"denied": "Denied",
"deniedApproval": "Denied Approval",
"all": "All",
"deny": "Deny",
"viewDetails": "View Details",
"requestingNewDeviceApproval": "requested a new device",
"resetFilters": "Сбросить фильтры",
"totalBlocked": "Запросы заблокированы Панголином",
"totalRequests": "Всего запросов",
@@ -729,16 +743,28 @@
"countries": "Страны",
"accessRoleCreate": "Создание роли",
"accessRoleCreateDescription": "Создайте новую роль для группы пользователей и выдавайте им разрешения.",
"accessRoleEdit": "Edit Role",
"accessRoleEditDescription": "Edit role information.",
"accessRoleCreateSubmit": "Создать роль",
"accessRoleCreated": "Роль создана",
"accessRoleCreatedDescription": "Роль была успешно создана.",
"accessRoleErrorCreate": "Не удалось создать роль",
"accessRoleErrorCreateDescription": "Произошла ошибка при создании роли.",
"accessRoleUpdateSubmit": "Update Role",
"accessRoleUpdated": "Role updated",
"accessRoleUpdatedDescription": "The role has been successfully updated.",
"accessApprovalUpdated": "Approval processed",
"accessApprovalApprovedDescription": "Set Approval Request decision to approved.",
"accessApprovalDeniedDescription": "Set Approval Request decision to denied.",
"accessRoleErrorUpdate": "Failed to update role",
"accessRoleErrorUpdateDescription": "An error occurred while updating the role.",
"accessApprovalErrorUpdate": "Failed to process approval",
"accessApprovalErrorUpdateDescription": "An error occurred while processing the approval.",
"accessRoleErrorNewRequired": "Новая роль обязательна",
"accessRoleErrorRemove": "Не удалось удалить роль",
"accessRoleErrorRemoveDescription": "Произошла ошибка при удалении роли.",
"accessRoleName": "Название роли",
"accessRoleQuestionRemove": "Вы собираетесь удалить роль {name}. Это действие нельзя отменить.",
"accessRoleQuestionRemove": "You're about to delete the `{name}` role. You cannot undo this action.",
"accessRoleRemove": "Удалить роль",
"accessRoleRemoveDescription": "Удалить роль из организации",
"accessRoleRemoveSubmit": "Удалить роль",
@@ -850,6 +876,7 @@
"orgPolicyConfig": "Настроить доступ для организации",
"idpUpdatedDescription": "Поставщик удостоверений успешно обновлён",
"redirectUrl": "URL редиректа",
"orgIdpRedirectUrls": "Перенаправление URL",
"redirectUrlAbout": "О редиректе URL",
"redirectUrlAboutDescription": "Это URL, на который пользователи будут перенаправлены после аутентификации. Вам нужно настроить этот URL в настройках провайдера.",
"pangolinAuth": "Аутентификация - Pangolin",
@@ -873,7 +900,7 @@
"inviteAlready": "Похоже, вы были приглашены!",
"inviteAlreadyDescription": "Чтобы принять приглашение, вы должны войти или создать учётную запись.",
"signupQuestion": "Уже есть учётная запись?",
"login": "Войти",
"login": "Log In",
"resourceNotFound": "Ресурс не найден",
"resourceNotFoundDescription": "Ресурс, к которому вы пытаетесь получить доступ, не существует.",
"pincodeRequirementsLength": "PIN должен состоять ровно из 6 цифр",
@@ -953,13 +980,13 @@
"passwordExpiryDescription": "Эта организация требует смены пароля каждые {maxDays} дней.",
"changePasswordNow": "Изменить пароль сейчас",
"pincodeAuth": "Код аутентификатора",
"pincodeSubmit2": "Отправить код",
"pincodeSubmit2": "Submit code",
"passwordResetSubmit": "Запросить сброс",
"passwordResetAlreadyHaveCode": "Введите код",
"passwordResetSmtpRequired": "Пожалуйста, обратитесь к администратору",
"passwordResetSmtpRequiredDescription": "Для сброса пароля необходим код сброса пароля. Обратитесь к администратору за помощью.",
"passwordBack": "Назад к паролю",
"loginBack": "Вернуться к входу",
"loginBack": "Go back to main login page",
"signup": "Регистрация",
"loginStart": "Войдите для начала работы",
"idpOidcTokenValidating": "Проверка OIDC токена",
@@ -1117,6 +1144,10 @@
"actionUpdateIdpOrg": "Обновить организацию IDP",
"actionCreateClient": "Создать Клиента",
"actionDeleteClient": "Удалить Клиента",
"actionArchiveClient": "Archive Client",
"actionUnarchiveClient": "Unarchive Client",
"actionBlockClient": "Block Client",
"actionUnblockClient": "Unblock Client",
"actionUpdateClient": "Обновить Клиента",
"actionListClients": "Список Клиентов",
"actionGetClient": "Получить Клиента",
@@ -1133,14 +1164,14 @@
"searchProgress": "Поиск...",
"create": "Создать",
"orgs": "Организации",
"loginError": "Произошла ошибка при входе",
"loginRequiredForDevice": "Для аутентификации устройства необходимо войти в систему.",
"loginError": "An unexpected error occurred. Please try again.",
"loginRequiredForDevice": "Login is required for your device.",
"passwordForgot": "Забыли пароль?",
"otpAuth": "Двухфакторная аутентификация",
"otpAuthDescription": "Введите код из вашего приложения-аутентификатора или один из ваших одноразовых резервных кодов.",
"otpAuthSubmit": "Отправить код",
"idpContinue": "Или продолжить с",
"otpAuthBack": "Вернуться к входу",
"otpAuthBack": "Back to Password",
"navbar": "Навигационное меню",
"navbarDescription": "Главное навигационное меню приложения",
"navbarDocsLink": "Документация",
@@ -1188,6 +1219,7 @@
"sidebarOverview": "Обзор",
"sidebarHome": "Главная",
"sidebarSites": "Сайты",
"sidebarApprovals": "Approval Requests",
"sidebarResources": "Ресурсы",
"sidebarProxyResources": "Публичный",
"sidebarClientResources": "Приватный",
@@ -1204,7 +1236,7 @@
"sidebarIdentityProviders": "Поставщики удостоверений",
"sidebarLicense": "Лицензия",
"sidebarClients": "Клиенты",
"sidebarUserDevices": "Пользователи",
"sidebarUserDevices": "User Devices",
"sidebarMachineClients": "Машины",
"sidebarDomains": "Домены",
"sidebarGeneral": "Управление",
@@ -1303,6 +1335,7 @@
"refreshError": "Не удалось обновить данные",
"verified": "Подтверждено",
"pending": "В ожидании",
"pendingApproval": "Pending Approval",
"sidebarBilling": "Выставление счетов",
"billing": "Выставление счетов",
"orgBillingDescription": "Управление платежной информацией и подписками",
@@ -1419,7 +1452,7 @@
"securityKeyRemoveSuccess": "Ключ безопасности успешно удален",
"securityKeyRemoveError": "Не удалось удалить ключ безопасности",
"securityKeyLoadError": "Не удалось загрузить ключи безопасности",
"securityKeyLogin": "Продолжить с ключом безопасности",
"securityKeyLogin": "Use Security Key",
"securityKeyAuthError": "Не удалось аутентифицироваться с ключом безопасности",
"securityKeyRecommendation": "Зарегистрируйте резервный ключ безопасности на другом устройстве, чтобы всегда иметь доступ к вашему аккаунту.",
"registering": "Регистрация...",
@@ -1479,7 +1512,7 @@
"IAgreeToThe": "Я согласен с",
"termsOfService": "условия использования",
"and": "и",
"privacyPolicy": "политика конфиденциальности"
"privacyPolicy": "политика конфиденциальности."
},
"signUpMarketing": {
"keepMeInTheLoop": "Держите меня в цикле с новостями, обновлениями и новыми функциями по электронной почте."
@@ -1546,6 +1579,8 @@
"IntervalSeconds": "Интервал здоровых состояний",
"timeoutSeconds": "Таймаут (сек)",
"timeIsInSeconds": "Время указано в секундах",
"requireDeviceApproval": "Require Device Approvals",
"requireDeviceApprovalDescription": "Users with this role need their devices approved by an admin before they can access resources",
"retryAttempts": "Количество попыток повторного запроса",
"expectedResponseCodes": "Ожидаемые коды ответов",
"expectedResponseCodesDescription": "HTTP-код состояния, указывающий на здоровое состояние. Если оставить пустым, 200-300 считается здоровым.",
@@ -1875,7 +1910,7 @@
"orgAuthChooseIdpDescription": "Выберите своего поставщика удостоверений личности для продолжения",
"orgAuthNoIdpConfigured": "Эта организация не имеет настроенных поставщиков идентификационных данных. Вместо этого вы можете войти в свой Pangolin.",
"orgAuthSignInWithPangolin": "Войти через Pangolin",
"orgAuthSignInToOrg": "Войдите в организацию",
"orgAuthSignInToOrg": "Sign in to an organization",
"orgAuthSelectOrgTitle": "Вход в организацию",
"orgAuthSelectOrgDescription": "Введите ID вашей организации, чтобы продолжить",
"orgAuthOrgIdPlaceholder": "ваша-организация",
@@ -2231,6 +2266,8 @@
"deviceCodeInvalidFormat": "Код должен быть 9 символов (например, A1AJ-N5JD)",
"deviceCodeInvalidOrExpired": "Неверный или просроченный код",
"deviceCodeVerifyFailed": "Не удалось проверить код устройства",
"deviceCodeValidating": "Validating device code...",
"deviceCodeVerifying": "Verifying device authorization...",
"signedInAs": "Вы вошли как",
"deviceCodeEnterPrompt": "Введите код, отображаемый на устройстве",
"continue": "Продолжить",
@@ -2243,7 +2280,7 @@
"deviceOrganizationsAccess": "Доступ ко всем организациям, к которым ваш аккаунт имеет доступ",
"deviceAuthorize": "Авторизовать {applicationName}",
"deviceConnected": "Устройство подключено!",
"deviceAuthorizedMessage": "Устройство авторизовано для доступа к вашей учетной записи.",
"deviceAuthorizedMessage": "Device is authorized to access your account. Please return to the client application.",
"pangolinCloud": "Облако Панголина",
"viewDevices": "Просмотр устройств",
"viewDevicesDescription": "Управление подключенными устройствами",
@@ -2305,6 +2342,7 @@
"identifier": "Identifier",
"deviceLoginUseDifferentAccount": "Не вы? Используйте другую учетную запись.",
"deviceLoginDeviceRequestingAccessToAccount": "Устройство запрашивает доступ к этой учетной записи.",
"loginSelectAuthenticationMethod": "Select an authentication method to continue.",
"noData": "Нет данных",
"machineClients": "Машинные клиенты",
"install": "Установить",
@@ -2349,6 +2387,7 @@
"enterConfirmation": "Введите подтверждение",
"blueprintViewDetails": "Подробности",
"defaultIdentityProvider": "Поставщик удостоверений по умолчанию",
"defaultIdentityProviderDescription": "Когда выбран поставщик идентификации по умолчанию, пользователь будет автоматически перенаправлен на провайдер для аутентификации.",
"editInternalResourceDialogNetworkSettings": "Настройки сети",
"editInternalResourceDialogAccessPolicy": "Политика доступа",
"editInternalResourceDialogAddRoles": "Добавить роли",
@@ -2392,5 +2431,56 @@
"maintenanceScreenTitle": "Сервис временно недоступен",
"maintenanceScreenMessage": "В настоящее время мы испытываем технические трудности. Пожалуйста, зайдите позже.",
"maintenanceScreenEstimatedCompletion": "Предполагаемое завершение:",
"createInternalResourceDialogDestinationRequired": "Укажите адрес назначения. Это может быть имя хоста или IP-адрес."
"createInternalResourceDialogDestinationRequired": "Укажите адрес назначения. Это может быть имя хоста или IP-адрес.",
"available": "Available",
"archived": "Archived",
"noArchivedDevices": "No archived devices found",
"deviceArchived": "Device archived",
"deviceArchivedDescription": "The device has been successfully archived.",
"errorArchivingDevice": "Error archiving device",
"failedToArchiveDevice": "Failed to archive device",
"deviceQuestionArchive": "Are you sure you want to archive this device?",
"deviceMessageArchive": "The device will be archived and removed from your active devices list.",
"deviceArchiveConfirm": "Archive Device",
"archiveDevice": "Archive Device",
"archive": "Archive",
"deviceUnarchived": "Device unarchived",
"deviceUnarchivedDescription": "The device has been successfully unarchived.",
"errorUnarchivingDevice": "Error unarchiving device",
"failedToUnarchiveDevice": "Failed to unarchive device",
"unarchive": "Unarchive",
"archiveClient": "Archive Client",
"archiveClientQuestion": "Are you sure you want to archive this client?",
"archiveClientMessage": "The client will be archived and removed from your active clients list.",
"archiveClientConfirm": "Archive Client",
"blockClient": "Block Client",
"blockClientQuestion": "Are you sure you want to block this client?",
"blockClientMessage": "The device will be forced to disconnect if currently connected. You can unblock the device later.",
"blockClientConfirm": "Block Client",
"active": "Active",
"usernameOrEmail": "Username or Email",
"selectYourOrganization": "Select your organization",
"signInTo": "Log in in to",
"signInWithPassword": "Continue with Password",
"noAuthMethodsAvailable": "No authentication methods available for this organization.",
"enterPassword": "Enter your password",
"enterMfaCode": "Enter the code from your authenticator app",
"securityKeyRequired": "Please use your security key to sign in.",
"needToUseAnotherAccount": "Need to use a different account?",
"loginLegalDisclaimer": "By clicking the buttons below, you acknowledge you have read, understand, and agree to the <termsOfService>Terms of Service</termsOfService> and <privacyPolicy>Privacy Policy</privacyPolicy>.",
"termsOfService": "Terms of Service",
"privacyPolicy": "Privacy Policy",
"userNotFoundWithUsername": "No user found with that username.",
"verify": "Verify",
"signIn": "Sign In",
"forgotPassword": "Forgot password?",
"orgSignInTip": "If you've logged in before, you can enter your username or email above to authenticate with your organization's identity provider instead. It's easier!",
"continueAnyway": "Continue anyway",
"dontShowAgain": "Don't show again",
"orgSignInNotice": "Did you know?",
"signupOrgNotice": "Trying to sign in?",
"signupOrgTip": "Are you trying to sign in through your organization's identity provider?",
"signupOrgLink": "Sign in or sign up with your organization instead",
"verifyEmailLogInWithDifferentAccount": "Use a Different Account",
"logIn": "Log In"
}

View File

@@ -257,6 +257,8 @@
"accessRolesSearch": "Rolleri ara...",
"accessRolesAdd": "Rol Ekle",
"accessRoleDelete": "Rolü Sil",
"accessApprovalsManage": "Manage Approvals",
"accessApprovalsDescription": "Manage approval requests in the organization",
"description": "Açıklama",
"inviteTitle": "Açık Davetiyeler",
"inviteDescription": "Organizasyona katılmak için diğer kullanıcılar için davetleri yönetin",
@@ -450,6 +452,18 @@
"selectDuration": "Süreyi seçin",
"selectResource": "Kaynak Seçin",
"filterByResource": "Kaynağa Göre Filtrele",
"selectApprovalState": "Select Approval State",
"filterByApprovalState": "Filter By Approval State",
"approvalListEmpty": "No approvals",
"approvalState": "Approval State",
"approve": "Approve",
"approved": "Approved",
"denied": "Denied",
"deniedApproval": "Denied Approval",
"all": "All",
"deny": "Deny",
"viewDetails": "View Details",
"requestingNewDeviceApproval": "requested a new device",
"resetFilters": "Filtreleri Sıfırla",
"totalBlocked": "Pangolin Tarafından Engellenen İstekler",
"totalRequests": "Toplam İstekler",
@@ -729,16 +743,28 @@
"countries": "Ülkeler",
"accessRoleCreate": "Rol Oluştur",
"accessRoleCreateDescription": "Kullanıcıları gruplamak ve izinlerini yönetmek için yeni bir rol oluşturun.",
"accessRoleEdit": "Edit Role",
"accessRoleEditDescription": "Edit role information.",
"accessRoleCreateSubmit": "Rol Oluştur",
"accessRoleCreated": "Rol oluşturuldu",
"accessRoleCreatedDescription": "Rol başarıyla oluşturuldu.",
"accessRoleErrorCreate": "Rol oluşturulamadı",
"accessRoleErrorCreateDescription": "Rol oluşturulurken bir hata oluştu.",
"accessRoleUpdateSubmit": "Update Role",
"accessRoleUpdated": "Role updated",
"accessRoleUpdatedDescription": "The role has been successfully updated.",
"accessApprovalUpdated": "Approval processed",
"accessApprovalApprovedDescription": "Set Approval Request decision to approved.",
"accessApprovalDeniedDescription": "Set Approval Request decision to denied.",
"accessRoleErrorUpdate": "Failed to update role",
"accessRoleErrorUpdateDescription": "An error occurred while updating the role.",
"accessApprovalErrorUpdate": "Failed to process approval",
"accessApprovalErrorUpdateDescription": "An error occurred while processing the approval.",
"accessRoleErrorNewRequired": "Yeni rol gerekli",
"accessRoleErrorRemove": "Rol kaldırılamadı",
"accessRoleErrorRemoveDescription": "Rol kaldırılırken bir hata oluştu.",
"accessRoleName": "Rol Adı",
"accessRoleQuestionRemove": "{name} rolünü silmek üzeresiniz. Bu eylemi geri alamazsınız.",
"accessRoleQuestionRemove": "You're about to delete the `{name}` role. You cannot undo this action.",
"accessRoleRemove": "Rolü Kaldır",
"accessRoleRemoveDescription": "Kuruluştan bir rol kaldır",
"accessRoleRemoveSubmit": "Rolü Kaldır",
@@ -850,6 +876,7 @@
"orgPolicyConfig": "Bir kuruluş için erişimi yapılandırın",
"idpUpdatedDescription": "Kimlik sağlayıcı başarıyla güncellendi",
"redirectUrl": "Yönlendirme URL'si",
"orgIdpRedirectUrls": "Yönlendirme URL'leri",
"redirectUrlAbout": "Yönlendirme URL'si Hakkında",
"redirectUrlAboutDescription": "Bu, kimlik doğrulamasından sonra kullanıcıların yönlendirileceği URL'dir. Bu URL'yi kimlik sağlayıcınızın ayarlarında yapılandırmanız gerekir.",
"pangolinAuth": "Yetkilendirme - Pangolin",
@@ -873,7 +900,7 @@
"inviteAlready": "Davetiye gönderilmiş gibi görünüyor!",
"inviteAlreadyDescription": "Daveti kabul etmek için giriş yapmalı veya bir hesap oluşturmalısınız.",
"signupQuestion": "Zaten bir hesabınız var mı?",
"login": "Giriş yap",
"login": "Log In",
"resourceNotFound": "No resources found",
"resourceNotFoundDescription": "Erişmeye çalıştığınız kaynak mevcut değil.",
"pincodeRequirementsLength": "PIN kesinlikle 6 haneli olmalıdır",
@@ -953,13 +980,13 @@
"passwordExpiryDescription": "Bu kuruluş, parolanızı {maxDays} günde bir değiştirmenizi gerektirir.",
"changePasswordNow": "Şifrenizi Şimdi Değiştirin",
"pincodeAuth": "Kimlik Doğrulama Kodu",
"pincodeSubmit2": "Kodu Gönder",
"pincodeSubmit2": "Submit code",
"passwordResetSubmit": "Sıfırlama İsteği",
"passwordResetAlreadyHaveCode": "Kodu Girin",
"passwordResetSmtpRequired": "Yönetici ile iletişime geçin",
"passwordResetSmtpRequiredDescription": "Parolanızı sıfırlamak için bir parola sıfırlama kodu gereklidir. Yardım için yönetici ile iletişime geçin.",
"passwordBack": "Şifreye Geri Dön",
"loginBack": "Girişe geri dön",
"loginBack": "Go back to main login page",
"signup": "Kaydol",
"loginStart": "Başlamak için giriş yapın",
"idpOidcTokenValidating": "OIDC token'ı doğrulanıyor",
@@ -1117,6 +1144,10 @@
"actionUpdateIdpOrg": "Kimlik Sağlayıcı Organizasyonu Güncelle",
"actionCreateClient": "Müşteri Oluştur",
"actionDeleteClient": "Müşteri Sil",
"actionArchiveClient": "Archive Client",
"actionUnarchiveClient": "Unarchive Client",
"actionBlockClient": "Block Client",
"actionUnblockClient": "Unblock Client",
"actionUpdateClient": "Müşteri Güncelle",
"actionListClients": "Müşterileri Listele",
"actionGetClient": "Müşteriyi Al",
@@ -1133,14 +1164,14 @@
"searchProgress": "Ara...",
"create": "Oluştur",
"orgs": "Organizasyonlar",
"loginError": "Giriş yaparken bir hata oluştu",
"loginRequiredForDevice": "Cihazınızı kimlik doğrulamak için giriş yapılması gereklidir.",
"loginError": "An unexpected error occurred. Please try again.",
"loginRequiredForDevice": "Login is required for your device.",
"passwordForgot": "Şifrenizi mi unuttunuz?",
"otpAuth": "İki Faktörlü Kimlik Doğrulama",
"otpAuthDescription": "Authenticator uygulamanızdan veya tek kullanımlık yedek kodlarınızdan birini girin.",
"otpAuthSubmit": "Kodu Gönder",
"idpContinue": "Veya devam et:",
"otpAuthBack": "Girişe Dön",
"otpAuthBack": "Back to Password",
"navbar": "Navigasyon Menüsü",
"navbarDescription": "Uygulamanın ana navigasyon menüsü",
"navbarDocsLink": "Dokümantasyon",
@@ -1188,6 +1219,7 @@
"sidebarOverview": "Genel Bakış",
"sidebarHome": "Ana Sayfa",
"sidebarSites": "Siteler",
"sidebarApprovals": "Approval Requests",
"sidebarResources": "Kaynaklar",
"sidebarProxyResources": "Herkese Açık",
"sidebarClientResources": "Özel",
@@ -1204,7 +1236,7 @@
"sidebarIdentityProviders": "Kimlik Sağlayıcılar",
"sidebarLicense": "Lisans",
"sidebarClients": "İstemciler",
"sidebarUserDevices": "Kullanıcılar",
"sidebarUserDevices": "User Devices",
"sidebarMachineClients": "Makineler",
"sidebarDomains": "Alan Adları",
"sidebarGeneral": "Yönet",
@@ -1303,6 +1335,7 @@
"refreshError": "Veriler yenilenemedi",
"verified": "Doğrulandı",
"pending": "Beklemede",
"pendingApproval": "Pending Approval",
"sidebarBilling": "Faturalama",
"billing": "Faturalama",
"orgBillingDescription": "Fatura bilgilerinizi ve aboneliklerinizi yönetin",
@@ -1419,7 +1452,7 @@
"securityKeyRemoveSuccess": "Güvenlik anahtarı başarıyla kaldırıldı",
"securityKeyRemoveError": "Güvenlik anahtarı kaldırılırken hata oluştu",
"securityKeyLoadError": "Güvenlik anahtarları yüklenirken hata oluştu",
"securityKeyLogin": "Güvenlik anahtarı ile devam edin",
"securityKeyLogin": "Use Security Key",
"securityKeyAuthError": "Güvenlik anahtarı ile kimlik doğrulama başarısız oldu",
"securityKeyRecommendation": "Hesabınızdan kilitlenmediğinizden emin olmak için farklı bir cihazda başka bir güvenlik anahtarı kaydetmeyi düşünün.",
"registering": "Kaydediliyor...",
@@ -1479,7 +1512,7 @@
"IAgreeToThe": "Kabul ediyorum",
"termsOfService": "hizmet şartları",
"and": "ve",
"privacyPolicy": "gizlilik politikası"
"privacyPolicy": "gizlilik politikası."
},
"signUpMarketing": {
"keepMeInTheLoop": "Bana e-posta yoluyla haberler, güncellemeler ve yeni özellikler hakkında bilgi verin."
@@ -1546,6 +1579,8 @@
"IntervalSeconds": "Sağlıklı Aralık",
"timeoutSeconds": "Zaman Aşımı (saniye)",
"timeIsInSeconds": "Zaman saniye cinsindendir",
"requireDeviceApproval": "Require Device Approvals",
"requireDeviceApprovalDescription": "Users with this role need their devices approved by an admin before they can access resources",
"retryAttempts": "Tekrar Deneme Girişimleri",
"expectedResponseCodes": "Beklenen Yanıt Kodları",
"expectedResponseCodesDescription": "Sağlıklı durumu gösteren HTTP durum kodu. Boş bırakılırsa, 200-300 arası sağlıklı kabul edilir.",
@@ -1875,7 +1910,7 @@
"orgAuthChooseIdpDescription": "Devam etmek için kimlik sağlayıcınızı seçin",
"orgAuthNoIdpConfigured": "Bu kuruluşta yapılandırılmış kimlik sağlayıcı yok. Bunun yerine Pangolin kimliğinizle giriş yapabilirsiniz.",
"orgAuthSignInWithPangolin": "Pangolin ile Giriş Yap",
"orgAuthSignInToOrg": "Bir kuruluşa giriş yapın",
"orgAuthSignInToOrg": "Sign in to an organization",
"orgAuthSelectOrgTitle": "Kuruluş Giriş",
"orgAuthSelectOrgDescription": "Devam etmek için kuruluş kimliğinizi girin",
"orgAuthOrgIdPlaceholder": "kuruluşunuz",
@@ -2231,6 +2266,8 @@
"deviceCodeInvalidFormat": "Kod 9 karakter olmalı (ör. A1AJ-N5JD)",
"deviceCodeInvalidOrExpired": "Geçersiz veya süresi dolmuş kod",
"deviceCodeVerifyFailed": "Cihaz kodu doğrulanamadı",
"deviceCodeValidating": "Validating device code...",
"deviceCodeVerifying": "Verifying device authorization...",
"signedInAs": "Olarak giriş yapıldı",
"deviceCodeEnterPrompt": "Cihazda gösterilen kodu girin",
"continue": "Devam Et",
@@ -2243,7 +2280,7 @@
"deviceOrganizationsAccess": "Hesabınızın erişim hakkına sahip olduğu tüm organizasyonlara erişim",
"deviceAuthorize": "{uygulamaAdi} yetkilendir",
"deviceConnected": "Cihaz Bağlandı!",
"deviceAuthorizedMessage": "Cihazınız, hesabınıza erişim izni almıştır.",
"deviceAuthorizedMessage": "Device is authorized to access your account. Please return to the client application.",
"pangolinCloud": "Pangolin Cloud",
"viewDevices": "Cihazları Görüntüle",
"viewDevicesDescription": "Bağlantılı cihazlarınızı yönetin",
@@ -2305,6 +2342,7 @@
"identifier": "Tanımlayıcı",
"deviceLoginUseDifferentAccount": "Siz değil misiniz? Farklı bir hesap kullanın.",
"deviceLoginDeviceRequestingAccessToAccount": "Bir cihaz bu hesaba erişim talep ediyor.",
"loginSelectAuthenticationMethod": "Select an authentication method to continue.",
"noData": "Veri Yok",
"machineClients": "Makine İstemcileri",
"install": "Yükle",
@@ -2349,6 +2387,7 @@
"enterConfirmation": "Onayı girin",
"blueprintViewDetails": "Detaylar",
"defaultIdentityProvider": "Varsayılan Kimlik Sağlayıcı",
"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ı",
"editInternalResourceDialogAccessPolicy": "Erişim Politikası",
"editInternalResourceDialogAddRoles": "Roller Ekle",
@@ -2392,5 +2431,56 @@
"maintenanceScreenTitle": "Servis Geçici Olarak Kullanılamıyor",
"maintenanceScreenMessage": "Şu anda teknik zorluklar yaşıyoruz. Lütfen yakında tekrar kontrol edin.",
"maintenanceScreenEstimatedCompletion": "Tahmini Tamamlama:",
"createInternalResourceDialogDestinationRequired": "Hedef gereklidir"
"createInternalResourceDialogDestinationRequired": "Hedef gereklidir",
"available": "Available",
"archived": "Archived",
"noArchivedDevices": "No archived devices found",
"deviceArchived": "Device archived",
"deviceArchivedDescription": "The device has been successfully archived.",
"errorArchivingDevice": "Error archiving device",
"failedToArchiveDevice": "Failed to archive device",
"deviceQuestionArchive": "Are you sure you want to archive this device?",
"deviceMessageArchive": "The device will be archived and removed from your active devices list.",
"deviceArchiveConfirm": "Archive Device",
"archiveDevice": "Archive Device",
"archive": "Archive",
"deviceUnarchived": "Device unarchived",
"deviceUnarchivedDescription": "The device has been successfully unarchived.",
"errorUnarchivingDevice": "Error unarchiving device",
"failedToUnarchiveDevice": "Failed to unarchive device",
"unarchive": "Unarchive",
"archiveClient": "Archive Client",
"archiveClientQuestion": "Are you sure you want to archive this client?",
"archiveClientMessage": "The client will be archived and removed from your active clients list.",
"archiveClientConfirm": "Archive Client",
"blockClient": "Block Client",
"blockClientQuestion": "Are you sure you want to block this client?",
"blockClientMessage": "The device will be forced to disconnect if currently connected. You can unblock the device later.",
"blockClientConfirm": "Block Client",
"active": "Active",
"usernameOrEmail": "Username or Email",
"selectYourOrganization": "Select your organization",
"signInTo": "Log in in to",
"signInWithPassword": "Continue with Password",
"noAuthMethodsAvailable": "No authentication methods available for this organization.",
"enterPassword": "Enter your password",
"enterMfaCode": "Enter the code from your authenticator app",
"securityKeyRequired": "Please use your security key to sign in.",
"needToUseAnotherAccount": "Need to use a different account?",
"loginLegalDisclaimer": "By clicking the buttons below, you acknowledge you have read, understand, and agree to the <termsOfService>Terms of Service</termsOfService> and <privacyPolicy>Privacy Policy</privacyPolicy>.",
"termsOfService": "Terms of Service",
"privacyPolicy": "Privacy Policy",
"userNotFoundWithUsername": "No user found with that username.",
"verify": "Verify",
"signIn": "Sign In",
"forgotPassword": "Forgot password?",
"orgSignInTip": "If you've logged in before, you can enter your username or email above to authenticate with your organization's identity provider instead. It's easier!",
"continueAnyway": "Continue anyway",
"dontShowAgain": "Don't show again",
"orgSignInNotice": "Did you know?",
"signupOrgNotice": "Trying to sign in?",
"signupOrgTip": "Are you trying to sign in through your organization's identity provider?",
"signupOrgLink": "Sign in or sign up with your organization instead",
"verifyEmailLogInWithDifferentAccount": "Use a Different Account",
"logIn": "Log In"
}

View File

@@ -257,6 +257,8 @@
"accessRolesSearch": "搜索角色...",
"accessRolesAdd": "添加角色",
"accessRoleDelete": "删除角色",
"accessApprovalsManage": "Manage Approvals",
"accessApprovalsDescription": "Manage approval requests in the organization",
"description": "描述",
"inviteTitle": "打开邀请",
"inviteDescription": "管理其他用户加入机构的邀请",
@@ -450,6 +452,18 @@
"selectDuration": "选择持续时间",
"selectResource": "选择资源",
"filterByResource": "按资源过滤",
"selectApprovalState": "Select Approval State",
"filterByApprovalState": "Filter By Approval State",
"approvalListEmpty": "No approvals",
"approvalState": "Approval State",
"approve": "Approve",
"approved": "Approved",
"denied": "Denied",
"deniedApproval": "Denied Approval",
"all": "All",
"deny": "Deny",
"viewDetails": "View Details",
"requestingNewDeviceApproval": "requested a new device",
"resetFilters": "重置过滤器",
"totalBlocked": "被Pangolin阻止的请求",
"totalRequests": "总请求",
@@ -729,16 +743,28 @@
"countries": "国家",
"accessRoleCreate": "创建角色",
"accessRoleCreateDescription": "创建一个新角色来分组用户并管理他们的权限。",
"accessRoleEdit": "Edit Role",
"accessRoleEditDescription": "Edit role information.",
"accessRoleCreateSubmit": "创建角色",
"accessRoleCreated": "角色已创建",
"accessRoleCreatedDescription": "角色已成功创建。",
"accessRoleErrorCreate": "创建角色失败",
"accessRoleErrorCreateDescription": "创建角色时出错。",
"accessRoleUpdateSubmit": "Update Role",
"accessRoleUpdated": "Role updated",
"accessRoleUpdatedDescription": "The role has been successfully updated.",
"accessApprovalUpdated": "Approval processed",
"accessApprovalApprovedDescription": "Set Approval Request decision to approved.",
"accessApprovalDeniedDescription": "Set Approval Request decision to denied.",
"accessRoleErrorUpdate": "Failed to update role",
"accessRoleErrorUpdateDescription": "An error occurred while updating the role.",
"accessApprovalErrorUpdate": "Failed to process approval",
"accessApprovalErrorUpdateDescription": "An error occurred while processing the approval.",
"accessRoleErrorNewRequired": "需要新角色",
"accessRoleErrorRemove": "删除角色失败",
"accessRoleErrorRemoveDescription": "删除角色时出错。",
"accessRoleName": "角色名称",
"accessRoleQuestionRemove": "您即将删除 {name} 角色。 此操作无法撤销。",
"accessRoleQuestionRemove": "You're about to delete the `{name}` role. You cannot undo this action.",
"accessRoleRemove": "删除角色",
"accessRoleRemoveDescription": "从组织中删除角色",
"accessRoleRemoveSubmit": "删除角色",
@@ -850,6 +876,7 @@
"orgPolicyConfig": "配置组织访问权限",
"idpUpdatedDescription": "身份提供商更新成功",
"redirectUrl": "重定向网址",
"orgIdpRedirectUrls": "重定向URL",
"redirectUrlAbout": "关于重定向网址",
"redirectUrlAboutDescription": "这是用户在验证后将被重定向到的URL。您需要在身份提供者的设置中配置此URL。",
"pangolinAuth": "认证 - Pangolin",
@@ -873,7 +900,7 @@
"inviteAlready": "看起来您已被邀请!",
"inviteAlreadyDescription": "要接受邀请,您必须登录或创建一个帐户。",
"signupQuestion": "已经有一个帐户?",
"login": "登录",
"login": "Log In",
"resourceNotFound": "找不到资源",
"resourceNotFoundDescription": "您要访问的资源不存在。",
"pincodeRequirementsLength": "PIN码必须是6位数字",
@@ -953,13 +980,13 @@
"passwordExpiryDescription": "该机构要求您每 {maxDays} 天更改一次密码。",
"changePasswordNow": "现在更改密码",
"pincodeAuth": "验证器代码",
"pincodeSubmit2": "提交代码",
"pincodeSubmit2": "Submit code",
"passwordResetSubmit": "请求重置",
"passwordResetAlreadyHaveCode": "输入代码",
"passwordResetSmtpRequired": "请联系您的管理员",
"passwordResetSmtpRequiredDescription": "需要密码重置密码。请联系您的管理员寻求帮助。",
"passwordBack": "回到密码",
"loginBack": "返回登录",
"loginBack": "Go back to main login page",
"signup": "注册",
"loginStart": "登录以开始",
"idpOidcTokenValidating": "正在验证 OIDC 令牌",
@@ -1117,6 +1144,10 @@
"actionUpdateIdpOrg": "更新 IDP组织",
"actionCreateClient": "创建客户端",
"actionDeleteClient": "删除客户端",
"actionArchiveClient": "Archive Client",
"actionUnarchiveClient": "Unarchive Client",
"actionBlockClient": "Block Client",
"actionUnblockClient": "Unblock Client",
"actionUpdateClient": "更新客户端",
"actionListClients": "列出客户端",
"actionGetClient": "获取客户端",
@@ -1133,14 +1164,14 @@
"searchProgress": "搜索中...",
"create": "创建",
"orgs": "组织",
"loginError": "登录时出错",
"loginRequiredForDevice": "需要登录才能验证您的设备。",
"loginError": "An unexpected error occurred. Please try again.",
"loginRequiredForDevice": "Login is required for your device.",
"passwordForgot": "忘记密码?",
"otpAuth": "两步验证",
"otpAuthDescription": "从您的身份验证程序中输入代码或您的单次备份代码。",
"otpAuthSubmit": "提交代码",
"idpContinue": "或者继续",
"otpAuthBack": "返回登录",
"otpAuthBack": "Back to Password",
"navbar": "导航菜单",
"navbarDescription": "应用程序的主导航菜单",
"navbarDocsLink": "文件",
@@ -1188,6 +1219,7 @@
"sidebarOverview": "概览",
"sidebarHome": "首页",
"sidebarSites": "站点",
"sidebarApprovals": "Approval Requests",
"sidebarResources": "资源",
"sidebarProxyResources": "公开的",
"sidebarClientResources": "非公开的",
@@ -1204,7 +1236,7 @@
"sidebarIdentityProviders": "身份提供商",
"sidebarLicense": "证书",
"sidebarClients": "客户端",
"sidebarUserDevices": "用户",
"sidebarUserDevices": "User Devices",
"sidebarMachineClients": "机",
"sidebarDomains": "域",
"sidebarGeneral": "管理",
@@ -1303,6 +1335,7 @@
"refreshError": "刷新数据失败",
"verified": "已验证",
"pending": "待定",
"pendingApproval": "Pending Approval",
"sidebarBilling": "计费",
"billing": "计费",
"orgBillingDescription": "管理账单信息和订阅",
@@ -1419,7 +1452,7 @@
"securityKeyRemoveSuccess": "安全密钥删除成功",
"securityKeyRemoveError": "删除安全密钥失败",
"securityKeyLoadError": "加载安全密钥失败",
"securityKeyLogin": "使用安全密钥继续",
"securityKeyLogin": "Use Security Key",
"securityKeyAuthError": "使用安全密钥认证失败",
"securityKeyRecommendation": "考虑在其他设备上注册另一个安全密钥,以确保不会被锁定在您的账户之外。",
"registering": "注册中...",
@@ -1479,7 +1512,7 @@
"IAgreeToThe": "我同意",
"termsOfService": "服务条款",
"and": "和",
"privacyPolicy": "隐私政策"
"privacyPolicy": "隐私政策"
},
"signUpMarketing": {
"keepMeInTheLoop": "通过电子邮件让我在循环中保持新闻、更新和新功能。"
@@ -1546,6 +1579,8 @@
"IntervalSeconds": "正常间隔",
"timeoutSeconds": "超时(秒)",
"timeIsInSeconds": "时间以秒为单位",
"requireDeviceApproval": "Require Device Approvals",
"requireDeviceApprovalDescription": "Users with this role need their devices approved by an admin before they can access resources",
"retryAttempts": "重试次数",
"expectedResponseCodes": "期望响应代码",
"expectedResponseCodesDescription": "HTTP 状态码表示健康状态。如留空200-300 被视为健康。",
@@ -1875,7 +1910,7 @@
"orgAuthChooseIdpDescription": "选择您的身份提供商以继续",
"orgAuthNoIdpConfigured": "此机构没有配置任何身份提供者。您可以使用您的 Pangolin 身份登录。",
"orgAuthSignInWithPangolin": "使用 Pangolin 登录",
"orgAuthSignInToOrg": "登录到一个组织",
"orgAuthSignInToOrg": "Sign in to an organization",
"orgAuthSelectOrgTitle": "组织登录",
"orgAuthSelectOrgDescription": "输入您的组织ID以继续",
"orgAuthOrgIdPlaceholder": "您的组织",
@@ -2231,6 +2266,8 @@
"deviceCodeInvalidFormat": "代码必须是9个字符(如A1AJ-N5JD)",
"deviceCodeInvalidOrExpired": "无效或过期的代码",
"deviceCodeVerifyFailed": "验证设备代码失败",
"deviceCodeValidating": "Validating device code...",
"deviceCodeVerifying": "Verifying device authorization...",
"signedInAs": "登录为",
"deviceCodeEnterPrompt": "输入设备上显示的代码",
"continue": "继续",
@@ -2243,7 +2280,7 @@
"deviceOrganizationsAccess": "访问您的帐户拥有访问权限的所有组织",
"deviceAuthorize": "授权{applicationName}",
"deviceConnected": "设备已连接!",
"deviceAuthorizedMessage": "设备被授权访问您的帐户。",
"deviceAuthorizedMessage": "Device is authorized to access your account. Please return to the client application.",
"pangolinCloud": "邦戈林云",
"viewDevices": "查看设备",
"viewDevicesDescription": "管理您已连接的设备",
@@ -2305,6 +2342,7 @@
"identifier": "Identifier",
"deviceLoginUseDifferentAccount": "不是你?使用一个不同的帐户。",
"deviceLoginDeviceRequestingAccessToAccount": "设备正在请求访问此帐户。",
"loginSelectAuthenticationMethod": "Select an authentication method to continue.",
"noData": "无数据",
"machineClients": "机器客户端",
"install": "安装",
@@ -2349,6 +2387,7 @@
"enterConfirmation": "输入确认",
"blueprintViewDetails": "详细信息",
"defaultIdentityProvider": "默认身份提供商",
"defaultIdentityProviderDescription": "当选择默认身份提供商时,用户将自动重定向到提供商进行身份验证。",
"editInternalResourceDialogNetworkSettings": "网络设置",
"editInternalResourceDialogAccessPolicy": "访问策略",
"editInternalResourceDialogAddRoles": "添加角色",
@@ -2392,5 +2431,56 @@
"maintenanceScreenTitle": "服务暂时不可用",
"maintenanceScreenMessage": "我们目前遇到技术问题。 请稍后再回来查看。",
"maintenanceScreenEstimatedCompletion": "预计完成时间:",
"createInternalResourceDialogDestinationRequired": "需要目标地址"
"createInternalResourceDialogDestinationRequired": "需要目标地址",
"available": "Available",
"archived": "Archived",
"noArchivedDevices": "No archived devices found",
"deviceArchived": "Device archived",
"deviceArchivedDescription": "The device has been successfully archived.",
"errorArchivingDevice": "Error archiving device",
"failedToArchiveDevice": "Failed to archive device",
"deviceQuestionArchive": "Are you sure you want to archive this device?",
"deviceMessageArchive": "The device will be archived and removed from your active devices list.",
"deviceArchiveConfirm": "Archive Device",
"archiveDevice": "Archive Device",
"archive": "Archive",
"deviceUnarchived": "Device unarchived",
"deviceUnarchivedDescription": "The device has been successfully unarchived.",
"errorUnarchivingDevice": "Error unarchiving device",
"failedToUnarchiveDevice": "Failed to unarchive device",
"unarchive": "Unarchive",
"archiveClient": "Archive Client",
"archiveClientQuestion": "Are you sure you want to archive this client?",
"archiveClientMessage": "The client will be archived and removed from your active clients list.",
"archiveClientConfirm": "Archive Client",
"blockClient": "Block Client",
"blockClientQuestion": "Are you sure you want to block this client?",
"blockClientMessage": "The device will be forced to disconnect if currently connected. You can unblock the device later.",
"blockClientConfirm": "Block Client",
"active": "Active",
"usernameOrEmail": "Username or Email",
"selectYourOrganization": "Select your organization",
"signInTo": "Log in in to",
"signInWithPassword": "Continue with Password",
"noAuthMethodsAvailable": "No authentication methods available for this organization.",
"enterPassword": "Enter your password",
"enterMfaCode": "Enter the code from your authenticator app",
"securityKeyRequired": "Please use your security key to sign in.",
"needToUseAnotherAccount": "Need to use a different account?",
"loginLegalDisclaimer": "By clicking the buttons below, you acknowledge you have read, understand, and agree to the <termsOfService>Terms of Service</termsOfService> and <privacyPolicy>Privacy Policy</privacyPolicy>.",
"termsOfService": "Terms of Service",
"privacyPolicy": "Privacy Policy",
"userNotFoundWithUsername": "No user found with that username.",
"verify": "Verify",
"signIn": "Sign In",
"forgotPassword": "Forgot password?",
"orgSignInTip": "If you've logged in before, you can enter your username or email above to authenticate with your organization's identity provider instead. It's easier!",
"continueAnyway": "Continue anyway",
"dontShowAgain": "Don't show again",
"orgSignInNotice": "Did you know?",
"signupOrgNotice": "Trying to sign in?",
"signupOrgTip": "Are you trying to sign in through your organization's identity provider?",
"signupOrgLink": "Sign in or sign up with your organization instead",
"verifyEmailLogInWithDifferentAccount": "Use a Different Account",
"logIn": "Log In"
}

View File

@@ -68,7 +68,7 @@ export const MAJOR_ASNS = [
code: "AS36351",
asn: 36351
},
// CDNs
{
name: "Cloudflare",
@@ -90,7 +90,7 @@ export const MAJOR_ASNS = [
code: "AS16625",
asn: 16625
},
// Mobile Carriers - US
{
name: "T-Mobile USA",
@@ -117,7 +117,7 @@ export const MAJOR_ASNS = [
code: "AS6430",
asn: 6430
},
// Mobile Carriers - Europe
{
name: "Vodafone UK",
@@ -144,7 +144,7 @@ export const MAJOR_ASNS = [
code: "AS12430",
asn: 12430
},
// Mobile Carriers - Asia
{
name: "NTT DoCoMo (Japan)",
@@ -176,7 +176,7 @@ export const MAJOR_ASNS = [
code: "AS9808",
asn: 9808
},
// Major US ISPs
{
name: "AT&T Services",
@@ -208,7 +208,7 @@ export const MAJOR_ASNS = [
code: "AS209",
asn: 209
},
// Major European ISPs
{
name: "Deutsche Telekom",
@@ -235,7 +235,7 @@ export const MAJOR_ASNS = [
code: "AS12956",
asn: 12956
},
// Major Asian ISPs
{
name: "China Telecom",
@@ -262,7 +262,7 @@ export const MAJOR_ASNS = [
code: "AS55836",
asn: 55836
},
// VPN/Proxy Providers
{
name: "Private Internet Access",
@@ -279,7 +279,7 @@ export const MAJOR_ASNS = [
code: "AS213281",
asn: 213281
},
// Social Media / Major Tech
{
name: "Facebook/Meta",
@@ -301,7 +301,7 @@ export const MAJOR_ASNS = [
code: "AS2906",
asn: 2906
},
// Academic/Research
{
name: "MIT",

View File

@@ -134,13 +134,15 @@ export const resources = pgTable("resources", {
proxyProtocol: boolean("proxyProtocol").notNull().default(false),
proxyProtocolVersion: integer("proxyProtocolVersion").default(1),
maintenanceModeEnabled: boolean("maintenanceModeEnabled").notNull().default(false),
maintenanceModeEnabled: boolean("maintenanceModeEnabled")
.notNull()
.default(false),
maintenanceModeType: text("maintenanceModeType", {
enum: ["forced", "automatic"]
}).default("forced"), // "forced" = always show, "automatic" = only when down
maintenanceTitle: text("maintenanceTitle"),
maintenanceMessage: text("maintenanceMessage"),
maintenanceEstimatedTime: text("maintenanceEstimatedTime"),
maintenanceEstimatedTime: text("maintenanceEstimatedTime")
});
export const targets = pgTable("targets", {
@@ -223,8 +225,8 @@ export const siteResources = pgTable("siteResources", {
enabled: boolean("enabled").notNull().default(true),
alias: varchar("alias"),
aliasAddress: varchar("aliasAddress"),
tcpPortRangeString: varchar("tcpPortRangeString"),
udpPortRangeString: varchar("udpPortRangeString"),
tcpPortRangeString: varchar("tcpPortRangeString").notNull().default("*"),
udpPortRangeString: varchar("udpPortRangeString").notNull().default("*"),
disableIcmp: boolean("disableIcmp").notNull().default(false)
});
@@ -464,13 +466,22 @@ export const resourceHeaderAuth = pgTable("resourceHeaderAuth", {
headerAuthHash: varchar("headerAuthHash").notNull()
});
export const resourceHeaderAuthExtendedCompatibility = pgTable("resourceHeaderAuthExtendedCompatibility", {
headerAuthExtendedCompatibilityId: serial("headerAuthExtendedCompatibilityId").primaryKey(),
resourceId: integer("resourceId")
.notNull()
.references(() => resources.resourceId, { onDelete: "cascade" }),
extendedCompatibilityIsActivated: boolean("extendedCompatibilityIsActivated").notNull().default(true),
});
export const resourceHeaderAuthExtendedCompatibility = pgTable(
"resourceHeaderAuthExtendedCompatibility",
{
headerAuthExtendedCompatibilityId: serial(
"headerAuthExtendedCompatibilityId"
).primaryKey(),
resourceId: integer("resourceId")
.notNull()
.references(() => resources.resourceId, { onDelete: "cascade" }),
extendedCompatibilityIsActivated: boolean(
"extendedCompatibilityIsActivated"
)
.notNull()
.default(true)
}
);
export const resourceAccessToken = pgTable("resourceAccessToken", {
accessTokenId: varchar("accessTokenId").primaryKey(),
@@ -872,7 +883,9 @@ export type ResourceSession = InferSelectModel<typeof resourceSessions>;
export type ResourcePincode = InferSelectModel<typeof resourcePincode>;
export type ResourcePassword = InferSelectModel<typeof resourcePassword>;
export type ResourceHeaderAuth = InferSelectModel<typeof resourceHeaderAuth>;
export type ResourceHeaderAuthExtendedCompatibility = InferSelectModel<typeof resourceHeaderAuthExtendedCompatibility>;
export type ResourceHeaderAuthExtendedCompatibility = InferSelectModel<
typeof resourceHeaderAuthExtendedCompatibility
>;
export type ResourceOtp = InferSelectModel<typeof resourceOtp>;
export type ResourceAccessToken = InferSelectModel<typeof resourceAccessToken>;
export type ResourceWhitelist = InferSelectModel<typeof resourceWhitelist>;

View File

@@ -1,6 +1,4 @@
import {
db, loginPage, LoginPage, loginPageOrg, Org, orgs,
} from "@server/db";
import { db, loginPage, LoginPage, loginPageOrg, Org, orgs } from "@server/db";
import {
Resource,
ResourcePassword,
@@ -27,7 +25,7 @@ export type ResourceWithAuth = {
pincode: ResourcePincode | null;
password: ResourcePassword | null;
headerAuth: ResourceHeaderAuth | null;
headerAuthExtendedCompatibility: ResourceHeaderAuthExtendedCompatibility | null
headerAuthExtendedCompatibility: ResourceHeaderAuthExtendedCompatibility | null;
org: Org;
};
@@ -59,12 +57,12 @@ export async function getResourceByDomain(
)
.leftJoin(
resourceHeaderAuthExtendedCompatibility,
eq(resourceHeaderAuthExtendedCompatibility.resourceId, resources.resourceId)
)
.innerJoin(
orgs,
eq(orgs.orgId, resources.orgId)
eq(
resourceHeaderAuthExtendedCompatibility.resourceId,
resources.resourceId
)
)
.innerJoin(orgs, eq(orgs.orgId, resources.orgId))
.where(eq(resources.fullDomain, domain))
.limit(1);
@@ -77,7 +75,8 @@ export async function getResourceByDomain(
pincode: result.resourcePincode,
password: result.resourcePassword,
headerAuth: result.resourceHeaderAuth,
headerAuthExtendedCompatibility: result.resourceHeaderAuthExtendedCompatibility,
headerAuthExtendedCompatibility:
result.resourceHeaderAuthExtendedCompatibility,
org: result.orgs
};
}

View File

@@ -12,22 +12,22 @@ import { no } from "zod/v4/locales";
export const domains = sqliteTable("domains", {
domainId: text("domainId").primaryKey(),
baseDomain: text("baseDomain").notNull(),
configManaged: integer("configManaged", {mode: "boolean"})
configManaged: integer("configManaged", { mode: "boolean" })
.notNull()
.default(false),
type: text("type"), // "ns", "cname", "wildcard"
verified: integer("verified", {mode: "boolean"}).notNull().default(false),
failed: integer("failed", {mode: "boolean"}).notNull().default(false),
verified: integer("verified", { mode: "boolean" }).notNull().default(false),
failed: integer("failed", { mode: "boolean" }).notNull().default(false),
tries: integer("tries").notNull().default(0),
certResolver: text("certResolver"),
preferWildcardCert: integer("preferWildcardCert", {mode: "boolean"})
preferWildcardCert: integer("preferWildcardCert", { mode: "boolean" })
});
export const dnsRecords = sqliteTable("dnsRecords", {
id: integer("id").primaryKey({autoIncrement: true}),
id: integer("id").primaryKey({ autoIncrement: true }),
domainId: text("domainId")
.notNull()
.references(() => domains.domainId, {onDelete: "cascade"}),
.references(() => domains.domainId, { onDelete: "cascade" }),
recordType: text("recordType").notNull(), // "NS" | "CNAME" | "A" | "TXT"
baseDomain: text("baseDomain"),
@@ -41,7 +41,7 @@ export const orgs = sqliteTable("orgs", {
subnet: text("subnet"),
utilitySubnet: text("utilitySubnet"), // this is the subnet for utility addresses
createdAt: text("createdAt"),
requireTwoFactor: integer("requireTwoFactor", {mode: "boolean"}),
requireTwoFactor: integer("requireTwoFactor", { mode: "boolean" }),
maxSessionLengthHours: integer("maxSessionLengthHours"), // hours
passwordExpiryDays: integer("passwordExpiryDays"), // days
settingsLogRetentionDaysRequest: integer("settingsLogRetentionDaysRequest") // where 0 = dont keep logs and -1 = keep forever and 9001 = end of the following year
@@ -58,23 +58,23 @@ export const orgs = sqliteTable("orgs", {
export const userDomains = sqliteTable("userDomains", {
userId: text("userId")
.notNull()
.references(() => users.userId, {onDelete: "cascade"}),
.references(() => users.userId, { onDelete: "cascade" }),
domainId: text("domainId")
.notNull()
.references(() => domains.domainId, {onDelete: "cascade"})
.references(() => domains.domainId, { onDelete: "cascade" })
});
export const orgDomains = sqliteTable("orgDomains", {
orgId: text("orgId")
.notNull()
.references(() => orgs.orgId, {onDelete: "cascade"}),
.references(() => orgs.orgId, { onDelete: "cascade" }),
domainId: text("domainId")
.notNull()
.references(() => domains.domainId, {onDelete: "cascade"})
.references(() => domains.domainId, { onDelete: "cascade" })
});
export const sites = sqliteTable("sites", {
siteId: integer("siteId").primaryKey({autoIncrement: true}),
siteId: integer("siteId").primaryKey({ autoIncrement: true }),
orgId: text("orgId")
.references(() => orgs.orgId, {
onDelete: "cascade"
@@ -91,7 +91,7 @@ export const sites = sqliteTable("sites", {
megabytesOut: integer("bytesOut").default(0),
lastBandwidthUpdate: text("lastBandwidthUpdate"),
type: text("type").notNull(), // "newt" or "wireguard"
online: integer("online", {mode: "boolean"}).notNull().default(false),
online: integer("online", { mode: "boolean" }).notNull().default(false),
// exit node stuff that is how to connect to the site when it has a wg server
address: text("address"), // this is the address of the wireguard interface in newt
@@ -99,14 +99,14 @@ export const sites = sqliteTable("sites", {
publicKey: text("publicKey"), // TODO: Fix typo in publicKey
lastHolePunch: integer("lastHolePunch"),
listenPort: integer("listenPort"),
dockerSocketEnabled: integer("dockerSocketEnabled", {mode: "boolean"})
dockerSocketEnabled: integer("dockerSocketEnabled", { mode: "boolean" })
.notNull()
.default(true)
});
export const resources = sqliteTable("resources", {
resourceId: integer("resourceId").primaryKey({autoIncrement: true}),
resourceGuid: text("resourceGuid", {length: 36})
resourceId: integer("resourceId").primaryKey({ autoIncrement: true }),
resourceGuid: text("resourceGuid", { length: 36 })
.unique()
.notNull()
.$defaultFn(() => randomUUID()),
@@ -122,35 +122,39 @@ export const resources = sqliteTable("resources", {
domainId: text("domainId").references(() => domains.domainId, {
onDelete: "set null"
}),
ssl: integer("ssl", {mode: "boolean"}).notNull().default(false),
blockAccess: integer("blockAccess", {mode: "boolean"})
ssl: integer("ssl", { mode: "boolean" }).notNull().default(false),
blockAccess: integer("blockAccess", { mode: "boolean" })
.notNull()
.default(false),
sso: integer("sso", {mode: "boolean"}).notNull().default(true),
http: integer("http", {mode: "boolean"}).notNull().default(true),
sso: integer("sso", { mode: "boolean" }).notNull().default(true),
http: integer("http", { mode: "boolean" }).notNull().default(true),
protocol: text("protocol").notNull(),
proxyPort: integer("proxyPort"),
emailWhitelistEnabled: integer("emailWhitelistEnabled", {mode: "boolean"})
emailWhitelistEnabled: integer("emailWhitelistEnabled", { mode: "boolean" })
.notNull()
.default(false),
applyRules: integer("applyRules", {mode: "boolean"})
applyRules: integer("applyRules", { mode: "boolean" })
.notNull()
.default(false),
enabled: integer("enabled", {mode: "boolean"}).notNull().default(true),
stickySession: integer("stickySession", {mode: "boolean"})
enabled: integer("enabled", { mode: "boolean" }).notNull().default(true),
stickySession: integer("stickySession", { mode: "boolean" })
.notNull()
.default(false),
tlsServerName: text("tlsServerName"),
setHostHeader: text("setHostHeader"),
enableProxy: integer("enableProxy", {mode: "boolean"}).default(true),
enableProxy: integer("enableProxy", { mode: "boolean" }).default(true),
skipToIdpId: integer("skipToIdpId").references(() => idp.idpId, {
onDelete: "set null"
}),
headers: text("headers"), // comma-separated list of headers to add to the request
proxyProtocol: integer("proxyProtocol", { mode: "boolean" }).notNull().default(false),
proxyProtocol: integer("proxyProtocol", { mode: "boolean" })
.notNull()
.default(false),
proxyProtocolVersion: integer("proxyProtocolVersion").default(1),
maintenanceModeEnabled: integer("maintenanceModeEnabled", { mode: "boolean" })
maintenanceModeEnabled: integer("maintenanceModeEnabled", {
mode: "boolean"
})
.notNull()
.default(false),
maintenanceModeType: text("maintenanceModeType", {
@@ -158,12 +162,11 @@ export const resources = sqliteTable("resources", {
}).default("forced"), // "forced" = always show, "automatic" = only when down
maintenanceTitle: text("maintenanceTitle"),
maintenanceMessage: text("maintenanceMessage"),
maintenanceEstimatedTime: text("maintenanceEstimatedTime"),
maintenanceEstimatedTime: text("maintenanceEstimatedTime")
});
export const targets = sqliteTable("targets", {
targetId: integer("targetId").primaryKey({autoIncrement: true}),
targetId: integer("targetId").primaryKey({ autoIncrement: true }),
resourceId: integer("resourceId")
.references(() => resources.resourceId, {
onDelete: "cascade"
@@ -178,7 +181,7 @@ export const targets = sqliteTable("targets", {
method: text("method"),
port: integer("port").notNull(),
internalPort: integer("internalPort"),
enabled: integer("enabled", {mode: "boolean"}).notNull().default(true),
enabled: integer("enabled", { mode: "boolean" }).notNull().default(true),
path: text("path"),
pathMatchType: text("pathMatchType"), // exact, prefix, regex
rewritePath: text("rewritePath"), // if set, rewrites the path to this value before sending to the target
@@ -192,8 +195,8 @@ export const targetHealthCheck = sqliteTable("targetHealthCheck", {
}),
targetId: integer("targetId")
.notNull()
.references(() => targets.targetId, {onDelete: "cascade"}),
hcEnabled: integer("hcEnabled", {mode: "boolean"})
.references(() => targets.targetId, { onDelete: "cascade" }),
hcEnabled: integer("hcEnabled", { mode: "boolean" })
.notNull()
.default(false),
hcPath: text("hcPath"),
@@ -215,7 +218,7 @@ export const targetHealthCheck = sqliteTable("targetHealthCheck", {
});
export const exitNodes = sqliteTable("exitNodes", {
exitNodeId: integer("exitNodeId").primaryKey({autoIncrement: true}),
exitNodeId: integer("exitNodeId").primaryKey({ autoIncrement: true }),
name: text("name").notNull(),
address: text("address").notNull(), // this is the address of the wireguard interface in gerbil
endpoint: text("endpoint").notNull(), // this is how to reach gerbil externally - gets put into the wireguard config
@@ -223,7 +226,7 @@ export const exitNodes = sqliteTable("exitNodes", {
listenPort: integer("listenPort").notNull(),
reachableAt: text("reachableAt"), // this is the internal address of the gerbil http server for command control
maxConnections: integer("maxConnections"),
online: integer("online", {mode: "boolean"}).notNull().default(false),
online: integer("online", { mode: "boolean" }).notNull().default(false),
lastPing: integer("lastPing"),
type: text("type").default("gerbil"), // gerbil, remoteExitNode
region: text("region")
@@ -236,10 +239,10 @@ export const siteResources = sqliteTable("siteResources", {
}),
siteId: integer("siteId")
.notNull()
.references(() => sites.siteId, {onDelete: "cascade"}),
.references(() => sites.siteId, { onDelete: "cascade" }),
orgId: text("orgId")
.notNull()
.references(() => orgs.orgId, {onDelete: "cascade"}),
.references(() => orgs.orgId, { onDelete: "cascade" }),
niceId: text("niceId").notNull(),
name: text("name").notNull(),
mode: text("mode").notNull(), // "host" | "cidr" | "port"
@@ -250,9 +253,9 @@ export const siteResources = sqliteTable("siteResources", {
enabled: integer("enabled", { mode: "boolean" }).notNull().default(true),
alias: text("alias"),
aliasAddress: text("aliasAddress"),
tcpPortRangeString: text("tcpPortRangeString"),
udpPortRangeString: text("udpPortRangeString"),
disableIcmp: integer("disableIcmp", { mode: "boolean" })
tcpPortRangeString: text("tcpPortRangeString").notNull().default("*"),
udpPortRangeString: text("udpPortRangeString").notNull().default("*"),
disableIcmp: integer("disableIcmp", { mode: "boolean" }).notNull().default(false)
});
export const clientSiteResources = sqliteTable("clientSiteResources", {
@@ -292,20 +295,20 @@ export const users = sqliteTable("user", {
onDelete: "cascade"
}),
passwordHash: text("passwordHash"),
twoFactorEnabled: integer("twoFactorEnabled", {mode: "boolean"})
twoFactorEnabled: integer("twoFactorEnabled", { mode: "boolean" })
.notNull()
.default(false),
twoFactorSetupRequested: integer("twoFactorSetupRequested", {
mode: "boolean"
}).default(false),
twoFactorSecret: text("twoFactorSecret"),
emailVerified: integer("emailVerified", {mode: "boolean"})
emailVerified: integer("emailVerified", { mode: "boolean" })
.notNull()
.default(false),
dateCreated: text("dateCreated").notNull(),
termsAcceptedTimestamp: text("termsAcceptedTimestamp"),
termsVersion: text("termsVersion"),
serverAdmin: integer("serverAdmin", {mode: "boolean"})
serverAdmin: integer("serverAdmin", { mode: "boolean" })
.notNull()
.default(false),
lastPasswordChange: integer("lastPasswordChange")
@@ -339,7 +342,7 @@ export const webauthnChallenge = sqliteTable("webauthnChallenge", {
export const setupTokens = sqliteTable("setupTokens", {
tokenId: text("tokenId").primaryKey(),
token: text("token").notNull(),
used: integer("used", {mode: "boolean"}).notNull().default(false),
used: integer("used", { mode: "boolean" }).notNull().default(false),
dateCreated: text("dateCreated").notNull(),
dateUsed: text("dateUsed")
});
@@ -378,7 +381,7 @@ export const clients = sqliteTable("clients", {
lastBandwidthUpdate: text("lastBandwidthUpdate"),
lastPing: integer("lastPing"),
type: text("type").notNull(), // "olm"
online: integer("online", {mode: "boolean"}).notNull().default(false),
online: integer("online", { mode: "boolean" }).notNull().default(false),
// endpoint: text("endpoint"),
lastHolePunch: integer("lastHolePunch")
});
@@ -424,10 +427,10 @@ export const olms = sqliteTable("olms", {
});
export const twoFactorBackupCodes = sqliteTable("twoFactorBackupCodes", {
codeId: integer("id").primaryKey({autoIncrement: true}),
codeId: integer("id").primaryKey({ autoIncrement: true }),
userId: text("userId")
.notNull()
.references(() => users.userId, {onDelete: "cascade"}),
.references(() => users.userId, { onDelete: "cascade" }),
codeHash: text("codeHash").notNull()
});
@@ -435,7 +438,7 @@ export const sessions = sqliteTable("session", {
sessionId: text("id").primaryKey(),
userId: text("userId")
.notNull()
.references(() => users.userId, {onDelete: "cascade"}),
.references(() => users.userId, { onDelete: "cascade" }),
expiresAt: integer("expiresAt").notNull(),
issuedAt: integer("issuedAt"),
deviceAuthUsed: integer("deviceAuthUsed", { mode: "boolean" })
@@ -447,7 +450,7 @@ export const newtSessions = sqliteTable("newtSession", {
sessionId: text("id").primaryKey(),
newtId: text("newtId")
.notNull()
.references(() => newts.newtId, {onDelete: "cascade"}),
.references(() => newts.newtId, { onDelete: "cascade" }),
expiresAt: integer("expiresAt").notNull()
});
@@ -455,14 +458,14 @@ export const olmSessions = sqliteTable("clientSession", {
sessionId: text("id").primaryKey(),
olmId: text("olmId")
.notNull()
.references(() => olms.olmId, {onDelete: "cascade"}),
.references(() => olms.olmId, { onDelete: "cascade" }),
expiresAt: integer("expiresAt").notNull()
});
export const userOrgs = sqliteTable("userOrgs", {
userId: text("userId")
.notNull()
.references(() => users.userId, {onDelete: "cascade"}),
.references(() => users.userId, { onDelete: "cascade" }),
orgId: text("orgId")
.references(() => orgs.orgId, {
onDelete: "cascade"
@@ -471,28 +474,28 @@ export const userOrgs = sqliteTable("userOrgs", {
roleId: integer("roleId")
.notNull()
.references(() => roles.roleId),
isOwner: integer("isOwner", {mode: "boolean"}).notNull().default(false),
isOwner: integer("isOwner", { mode: "boolean" }).notNull().default(false),
autoProvisioned: integer("autoProvisioned", {
mode: "boolean"
}).default(false)
});
export const emailVerificationCodes = sqliteTable("emailVerificationCodes", {
codeId: integer("id").primaryKey({autoIncrement: true}),
codeId: integer("id").primaryKey({ autoIncrement: true }),
userId: text("userId")
.notNull()
.references(() => users.userId, {onDelete: "cascade"}),
.references(() => users.userId, { onDelete: "cascade" }),
email: text("email").notNull(),
code: text("code").notNull(),
expiresAt: integer("expiresAt").notNull()
});
export const passwordResetTokens = sqliteTable("passwordResetTokens", {
tokenId: integer("id").primaryKey({autoIncrement: true}),
tokenId: integer("id").primaryKey({ autoIncrement: true }),
email: text("email").notNull(),
userId: text("userId")
.notNull()
.references(() => users.userId, {onDelete: "cascade"}),
.references(() => users.userId, { onDelete: "cascade" }),
tokenHash: text("tokenHash").notNull(),
expiresAt: integer("expiresAt").notNull()
});
@@ -504,13 +507,13 @@ export const actions = sqliteTable("actions", {
});
export const roles = sqliteTable("roles", {
roleId: integer("roleId").primaryKey({autoIncrement: true}),
roleId: integer("roleId").primaryKey({ autoIncrement: true }),
orgId: text("orgId")
.references(() => orgs.orgId, {
onDelete: "cascade"
})
.notNull(),
isAdmin: integer("isAdmin", {mode: "boolean"}),
isAdmin: integer("isAdmin", { mode: "boolean" }),
name: text("name").notNull(),
description: text("description")
});
@@ -518,92 +521,92 @@ export const roles = sqliteTable("roles", {
export const roleActions = sqliteTable("roleActions", {
roleId: integer("roleId")
.notNull()
.references(() => roles.roleId, {onDelete: "cascade"}),
.references(() => roles.roleId, { onDelete: "cascade" }),
actionId: text("actionId")
.notNull()
.references(() => actions.actionId, {onDelete: "cascade"}),
.references(() => actions.actionId, { onDelete: "cascade" }),
orgId: text("orgId")
.notNull()
.references(() => orgs.orgId, {onDelete: "cascade"})
.references(() => orgs.orgId, { onDelete: "cascade" })
});
export const userActions = sqliteTable("userActions", {
userId: text("userId")
.notNull()
.references(() => users.userId, {onDelete: "cascade"}),
.references(() => users.userId, { onDelete: "cascade" }),
actionId: text("actionId")
.notNull()
.references(() => actions.actionId, {onDelete: "cascade"}),
.references(() => actions.actionId, { onDelete: "cascade" }),
orgId: text("orgId")
.notNull()
.references(() => orgs.orgId, {onDelete: "cascade"})
.references(() => orgs.orgId, { onDelete: "cascade" })
});
export const roleSites = sqliteTable("roleSites", {
roleId: integer("roleId")
.notNull()
.references(() => roles.roleId, {onDelete: "cascade"}),
.references(() => roles.roleId, { onDelete: "cascade" }),
siteId: integer("siteId")
.notNull()
.references(() => sites.siteId, {onDelete: "cascade"})
.references(() => sites.siteId, { onDelete: "cascade" })
});
export const userSites = sqliteTable("userSites", {
userId: text("userId")
.notNull()
.references(() => users.userId, {onDelete: "cascade"}),
.references(() => users.userId, { onDelete: "cascade" }),
siteId: integer("siteId")
.notNull()
.references(() => sites.siteId, {onDelete: "cascade"})
.references(() => sites.siteId, { onDelete: "cascade" })
});
export const userClients = sqliteTable("userClients", {
userId: text("userId")
.notNull()
.references(() => users.userId, {onDelete: "cascade"}),
.references(() => users.userId, { onDelete: "cascade" }),
clientId: integer("clientId")
.notNull()
.references(() => clients.clientId, {onDelete: "cascade"})
.references(() => clients.clientId, { onDelete: "cascade" })
});
export const roleClients = sqliteTable("roleClients", {
roleId: integer("roleId")
.notNull()
.references(() => roles.roleId, {onDelete: "cascade"}),
.references(() => roles.roleId, { onDelete: "cascade" }),
clientId: integer("clientId")
.notNull()
.references(() => clients.clientId, {onDelete: "cascade"})
.references(() => clients.clientId, { onDelete: "cascade" })
});
export const roleResources = sqliteTable("roleResources", {
roleId: integer("roleId")
.notNull()
.references(() => roles.roleId, {onDelete: "cascade"}),
.references(() => roles.roleId, { onDelete: "cascade" }),
resourceId: integer("resourceId")
.notNull()
.references(() => resources.resourceId, {onDelete: "cascade"})
.references(() => resources.resourceId, { onDelete: "cascade" })
});
export const userResources = sqliteTable("userResources", {
userId: text("userId")
.notNull()
.references(() => users.userId, {onDelete: "cascade"}),
.references(() => users.userId, { onDelete: "cascade" }),
resourceId: integer("resourceId")
.notNull()
.references(() => resources.resourceId, {onDelete: "cascade"})
.references(() => resources.resourceId, { onDelete: "cascade" })
});
export const userInvites = sqliteTable("userInvites", {
inviteId: text("inviteId").primaryKey(),
orgId: text("orgId")
.notNull()
.references(() => orgs.orgId, {onDelete: "cascade"}),
.references(() => orgs.orgId, { onDelete: "cascade" }),
email: text("email").notNull(),
expiresAt: integer("expiresAt").notNull(),
tokenHash: text("token").notNull(),
roleId: integer("roleId")
.notNull()
.references(() => roles.roleId, {onDelete: "cascade"})
.references(() => roles.roleId, { onDelete: "cascade" })
});
export const resourcePincode = sqliteTable("resourcePincode", {
@@ -612,7 +615,7 @@ export const resourcePincode = sqliteTable("resourcePincode", {
}),
resourceId: integer("resourceId")
.notNull()
.references(() => resources.resourceId, {onDelete: "cascade"}),
.references(() => resources.resourceId, { onDelete: "cascade" }),
pincodeHash: text("pincodeHash").notNull(),
digitLength: integer("digitLength").notNull()
});
@@ -623,7 +626,7 @@ export const resourcePassword = sqliteTable("resourcePassword", {
}),
resourceId: integer("resourceId")
.notNull()
.references(() => resources.resourceId, {onDelete: "cascade"}),
.references(() => resources.resourceId, { onDelete: "cascade" }),
passwordHash: text("passwordHash").notNull()
});
@@ -633,28 +636,38 @@ export const resourceHeaderAuth = sqliteTable("resourceHeaderAuth", {
}),
resourceId: integer("resourceId")
.notNull()
.references(() => resources.resourceId, {onDelete: "cascade"}),
.references(() => resources.resourceId, { onDelete: "cascade" }),
headerAuthHash: text("headerAuthHash").notNull()
});
export const resourceHeaderAuthExtendedCompatibility = sqliteTable("resourceHeaderAuthExtendedCompatibility", {
headerAuthExtendedCompatibilityId: integer("headerAuthExtendedCompatibilityId").primaryKey({
autoIncrement: true
}),
resourceId: integer("resourceId")
.notNull()
.references(() => resources.resourceId, {onDelete: "cascade"}),
extendedCompatibilityIsActivated: integer("extendedCompatibilityIsActivated", {mode: "boolean"}).notNull().default(true)
});
export const resourceHeaderAuthExtendedCompatibility = sqliteTable(
"resourceHeaderAuthExtendedCompatibility",
{
headerAuthExtendedCompatibilityId: integer(
"headerAuthExtendedCompatibilityId"
).primaryKey({
autoIncrement: true
}),
resourceId: integer("resourceId")
.notNull()
.references(() => resources.resourceId, { onDelete: "cascade" }),
extendedCompatibilityIsActivated: integer(
"extendedCompatibilityIsActivated",
{ mode: "boolean" }
)
.notNull()
.default(true)
}
);
export const resourceAccessToken = sqliteTable("resourceAccessToken", {
accessTokenId: text("accessTokenId").primaryKey(),
orgId: text("orgId")
.notNull()
.references(() => orgs.orgId, {onDelete: "cascade"}),
.references(() => orgs.orgId, { onDelete: "cascade" }),
resourceId: integer("resourceId")
.notNull()
.references(() => resources.resourceId, {onDelete: "cascade"}),
.references(() => resources.resourceId, { onDelete: "cascade" }),
tokenHash: text("tokenHash").notNull(),
sessionLength: integer("sessionLength").notNull(),
expiresAt: integer("expiresAt"),
@@ -667,13 +680,13 @@ export const resourceSessions = sqliteTable("resourceSessions", {
sessionId: text("id").primaryKey(),
resourceId: integer("resourceId")
.notNull()
.references(() => resources.resourceId, {onDelete: "cascade"}),
.references(() => resources.resourceId, { onDelete: "cascade" }),
expiresAt: integer("expiresAt").notNull(),
sessionLength: integer("sessionLength").notNull(),
doNotExtend: integer("doNotExtend", {mode: "boolean"})
doNotExtend: integer("doNotExtend", { mode: "boolean" })
.notNull()
.default(false),
isRequestToken: integer("isRequestToken", {mode: "boolean"}),
isRequestToken: integer("isRequestToken", { mode: "boolean" }),
userSessionId: text("userSessionId").references(() => sessions.sessionId, {
onDelete: "cascade"
}),
@@ -705,11 +718,11 @@ export const resourceSessions = sqliteTable("resourceSessions", {
});
export const resourceWhitelist = sqliteTable("resourceWhitelist", {
whitelistId: integer("id").primaryKey({autoIncrement: true}),
whitelistId: integer("id").primaryKey({ autoIncrement: true }),
email: text("email").notNull(),
resourceId: integer("resourceId")
.notNull()
.references(() => resources.resourceId, {onDelete: "cascade"})
.references(() => resources.resourceId, { onDelete: "cascade" })
});
export const resourceOtp = sqliteTable("resourceOtp", {
@@ -718,7 +731,7 @@ export const resourceOtp = sqliteTable("resourceOtp", {
}),
resourceId: integer("resourceId")
.notNull()
.references(() => resources.resourceId, {onDelete: "cascade"}),
.references(() => resources.resourceId, { onDelete: "cascade" }),
email: text("email").notNull(),
otpHash: text("otpHash").notNull(),
expiresAt: integer("expiresAt").notNull()
@@ -730,11 +743,11 @@ export const versionMigrations = sqliteTable("versionMigrations", {
});
export const resourceRules = sqliteTable("resourceRules", {
ruleId: integer("ruleId").primaryKey({autoIncrement: true}),
ruleId: integer("ruleId").primaryKey({ autoIncrement: true }),
resourceId: integer("resourceId")
.notNull()
.references(() => resources.resourceId, {onDelete: "cascade"}),
enabled: integer("enabled", {mode: "boolean"}).notNull().default(true),
.references(() => resources.resourceId, { onDelete: "cascade" }),
enabled: integer("enabled", { mode: "boolean" }).notNull().default(true),
priority: integer("priority").notNull(),
action: text("action").notNull(), // ACCEPT, DROP, PASS
match: text("match").notNull(), // CIDR, PATH, IP
@@ -742,17 +755,17 @@ export const resourceRules = sqliteTable("resourceRules", {
});
export const supporterKey = sqliteTable("supporterKey", {
keyId: integer("keyId").primaryKey({autoIncrement: true}),
keyId: integer("keyId").primaryKey({ autoIncrement: true }),
key: text("key").notNull(),
githubUsername: text("githubUsername").notNull(),
phrase: text("phrase"),
tier: text("tier"),
valid: integer("valid", {mode: "boolean"}).notNull().default(false)
valid: integer("valid", { mode: "boolean" }).notNull().default(false)
});
// Identity Providers
export const idp = sqliteTable("idp", {
idpId: integer("idpId").primaryKey({autoIncrement: true}),
idpId: integer("idpId").primaryKey({ autoIncrement: true }),
name: text("name").notNull(),
type: text("type").notNull(),
defaultRoleMapping: text("defaultRoleMapping"),
@@ -772,7 +785,7 @@ export const idpOidcConfig = sqliteTable("idpOidcConfig", {
variant: text("variant").notNull().default("oidc"),
idpId: integer("idpId")
.notNull()
.references(() => idp.idpId, {onDelete: "cascade"}),
.references(() => idp.idpId, { onDelete: "cascade" }),
clientId: text("clientId").notNull(),
clientSecret: text("clientSecret").notNull(),
authUrl: text("authUrl").notNull(),
@@ -800,22 +813,22 @@ export const apiKeys = sqliteTable("apiKeys", {
apiKeyHash: text("apiKeyHash").notNull(),
lastChars: text("lastChars").notNull(),
createdAt: text("dateCreated").notNull(),
isRoot: integer("isRoot", {mode: "boolean"}).notNull().default(false)
isRoot: integer("isRoot", { mode: "boolean" }).notNull().default(false)
});
export const apiKeyActions = sqliteTable("apiKeyActions", {
apiKeyId: text("apiKeyId")
.notNull()
.references(() => apiKeys.apiKeyId, {onDelete: "cascade"}),
.references(() => apiKeys.apiKeyId, { onDelete: "cascade" }),
actionId: text("actionId")
.notNull()
.references(() => actions.actionId, {onDelete: "cascade"})
.references(() => actions.actionId, { onDelete: "cascade" })
});
export const apiKeyOrg = sqliteTable("apiKeyOrg", {
apiKeyId: text("apiKeyId")
.notNull()
.references(() => apiKeys.apiKeyId, {onDelete: "cascade"}),
.references(() => apiKeys.apiKeyId, { onDelete: "cascade" }),
orgId: text("orgId")
.references(() => orgs.orgId, {
onDelete: "cascade"
@@ -826,10 +839,10 @@ export const apiKeyOrg = sqliteTable("apiKeyOrg", {
export const idpOrg = sqliteTable("idpOrg", {
idpId: integer("idpId")
.notNull()
.references(() => idp.idpId, {onDelete: "cascade"}),
.references(() => idp.idpId, { onDelete: "cascade" }),
orgId: text("orgId")
.notNull()
.references(() => orgs.orgId, {onDelete: "cascade"}),
.references(() => orgs.orgId, { onDelete: "cascade" }),
roleMapping: text("roleMapping"),
orgMapping: text("orgMapping")
});
@@ -847,19 +860,19 @@ export const blueprints = sqliteTable("blueprints", {
name: text("name").notNull(),
source: text("source").notNull(),
createdAt: integer("createdAt").notNull(),
succeeded: integer("succeeded", {mode: "boolean"}).notNull(),
succeeded: integer("succeeded", { mode: "boolean" }).notNull(),
contents: text("contents").notNull(),
message: text("message")
});
export const requestAuditLog = sqliteTable(
"requestAuditLog",
{
id: integer("id").primaryKey({autoIncrement: true}),
id: integer("id").primaryKey({ autoIncrement: true }),
timestamp: integer("timestamp").notNull(), // this is EPOCH time in seconds
orgId: text("orgId").references(() => orgs.orgId, {
onDelete: "cascade"
}),
action: integer("action", {mode: "boolean"}).notNull(),
action: integer("action", { mode: "boolean" }).notNull(),
reason: integer("reason").notNull(),
actorType: text("actorType"),
actor: text("actor"),
@@ -876,7 +889,7 @@ export const requestAuditLog = sqliteTable(
host: text("host"),
path: text("path"),
method: text("method"),
tls: integer("tls", {mode: "boolean"})
tls: integer("tls", { mode: "boolean" })
},
(table) => [
index("idx_requestAuditLog_timestamp").on(table.timestamp),
@@ -932,7 +945,9 @@ export type ResourceSession = InferSelectModel<typeof resourceSessions>;
export type ResourcePincode = InferSelectModel<typeof resourcePincode>;
export type ResourcePassword = InferSelectModel<typeof resourcePassword>;
export type ResourceHeaderAuth = InferSelectModel<typeof resourceHeaderAuth>;
export type ResourceHeaderAuthExtendedCompatibility = InferSelectModel<typeof resourceHeaderAuthExtendedCompatibility>;
export type ResourceHeaderAuthExtendedCompatibility = InferSelectModel<
typeof resourceHeaderAuthExtendedCompatibility
>;
export type ResourceOtp = InferSelectModel<typeof resourceOtp>;
export type ResourceAccessToken = InferSelectModel<typeof resourceAccessToken>;
export type ResourceWhitelist = InferSelectModel<typeof resourceWhitelist>;

View File

@@ -0,0 +1,3 @@
import { z } from "zod";
export const MaintenanceSchema = z.object({});

View File

@@ -1,4 +1,14 @@
import { db, newts, blueprints, Blueprint, Site, siteResources, roleSiteResources, userSiteResources, clientSiteResources } from "@server/db";
import {
db,
newts,
blueprints,
Blueprint,
Site,
siteResources,
roleSiteResources,
userSiteResources,
clientSiteResources
} from "@server/db";
import { Config, ConfigSchema } from "./types";
import { ProxyResourcesResults, updateProxyResources } from "./proxyResources";
import { fromError } from "zod-validation-error";
@@ -126,7 +136,7 @@ export async function applyBlueprint({
)
.then((rows) => rows.map((row) => row.roleId));
const existingUserIds= await trx
const existingUserIds = await trx
.select()
.from(userSiteResources)
.where(
@@ -134,7 +144,8 @@ export async function applyBlueprint({
userSiteResources.siteResourceId,
result.oldSiteResource.siteResourceId
)
).then((rows) => rows.map((row) => row.userId));
)
.then((rows) => rows.map((row) => row.userId));
const existingClientIds = await trx
.select()
@@ -144,13 +155,19 @@ export async function applyBlueprint({
clientSiteResources.siteResourceId,
result.oldSiteResource.siteResourceId
)
).then((rows) => rows.map((row) => row.clientId));
)
.then((rows) => rows.map((row) => row.clientId));
// delete the existing site resource
await trx
.delete(siteResources)
.where(
and(eq(siteResources.siteResourceId, result.oldSiteResource.siteResourceId))
and(
eq(
siteResources.siteResourceId,
result.oldSiteResource.siteResourceId
)
)
);
await rebuildClientAssociationsFromSiteResource(
@@ -161,7 +178,7 @@ export async function applyBlueprint({
const [insertedSiteResource] = await trx
.insert(siteResources)
.values({
...result.newSiteResource,
...result.newSiteResource
})
.returning();
@@ -172,18 +189,20 @@ export async function applyBlueprint({
if (existingRoleIds.length > 0) {
await trx.insert(roleSiteResources).values(
existingRoleIds.map((roleId) => ({
existingRoleIds.map((roleId) => ({
roleId,
siteResourceId: insertedSiteResource!.siteResourceId
siteResourceId:
insertedSiteResource!.siteResourceId
}))
);
}
if (existingUserIds.length > 0) {
await trx.insert(userSiteResources).values(
existingUserIds.map((userId) => ({
existingUserIds.map((userId) => ({
userId,
siteResourceId: insertedSiteResource!.siteResourceId
siteResourceId:
insertedSiteResource!.siteResourceId
}))
);
}
@@ -192,7 +211,8 @@ export async function applyBlueprint({
await trx.insert(clientSiteResources).values(
existingClientIds.map((clientId) => ({
clientId,
siteResourceId: insertedSiteResource!.siteResourceId
siteResourceId:
insertedSiteResource!.siteResourceId
}))
);
}
@@ -201,7 +221,6 @@ export async function applyBlueprint({
insertedSiteResource,
trx
);
} else {
const [newSite] = await trx
.select()

View File

@@ -2,7 +2,8 @@ import {
domains,
orgDomains,
Resource,
resourceHeaderAuth, resourceHeaderAuthExtendedCompatibility,
resourceHeaderAuth,
resourceHeaderAuthExtendedCompatibility,
resourcePincode,
resourceRules,
resourceWhitelist,
@@ -16,8 +17,8 @@ import {
userResources,
users
} from "@server/db";
import {resources, targets, sites} from "@server/db";
import {eq, and, asc, or, ne, count, isNotNull} from "drizzle-orm";
import { resources, targets, sites } from "@server/db";
import { eq, and, asc, or, ne, count, isNotNull } from "drizzle-orm";
import {
Config,
ConfigSchema,
@@ -25,12 +26,13 @@ import {
TargetData
} from "./types";
import logger from "@server/logger";
import {createCertificate} from "#dynamic/routers/certificates/createCertificate";
import {pickPort} from "@server/routers/target/helpers";
import {resourcePassword} from "@server/db";
import {hashPassword} from "@server/auth/password";
import {isValidCIDR, isValidIP, isValidUrlGlobPattern} from "../validators";
import {get} from "http";
import { createCertificate } from "#dynamic/routers/certificates/createCertificate";
import { pickPort } from "@server/routers/target/helpers";
import { resourcePassword } from "@server/db";
import { hashPassword } from "@server/auth/password";
import { isValidCIDR, isValidIP, isValidUrlGlobPattern } from "../validators";
import { isLicensedOrSubscribed } from "../isLicencedOrSubscribed";
import { build } from "@server/build";
export type ProxyResourcesResults = {
proxyResource: Resource;
@@ -63,7 +65,7 @@ export async function updateProxyResources(
if (targetSiteId) {
// Look up site by niceId
[site] = await trx
.select({siteId: sites.siteId})
.select({ siteId: sites.siteId })
.from(sites)
.where(
and(
@@ -75,7 +77,7 @@ export async function updateProxyResources(
} else if (siteId) {
// Use the provided siteId directly, but verify it belongs to the org
[site] = await trx
.select({siteId: sites.siteId})
.select({ siteId: sites.siteId })
.from(sites)
.where(
and(eq(sites.siteId, siteId), eq(sites.orgId, orgId))
@@ -93,7 +95,7 @@ export async function updateProxyResources(
let internalPortToCreate;
if (!targetData["internal-port"]) {
const {internalPort, targetIps} = await pickPort(
const { internalPort, targetIps } = await pickPort(
site.siteId!,
trx
);
@@ -209,6 +211,16 @@ export async function updateProxyResources(
resource = existingResource;
} else {
// Update existing resource
const isLicensed = await isLicensedOrSubscribed(orgId);
if (build == "enterprise" && !isLicensed) {
logger.warn(
"Server is not licensed! Clearing set maintenance screen values"
);
// null the maintenance mode fields if not licensed
resourceData.maintenance = undefined;
}
[resource] = await trx
.update(resources)
.set({
@@ -228,12 +240,19 @@ export async function updateProxyResources(
tlsServerName: resourceData["tls-server-name"] || null,
emailWhitelistEnabled: resourceData.auth?.[
"whitelist-users"
]
]
? resourceData.auth["whitelist-users"].length > 0
: false,
headers: headers || null,
applyRules:
resourceData.rules && resourceData.rules.length > 0
resourceData.rules && resourceData.rules.length > 0,
maintenanceModeEnabled:
resourceData.maintenance?.enabled,
maintenanceModeType: resourceData.maintenance?.type,
maintenanceTitle: resourceData.maintenance?.title,
maintenanceMessage: resourceData.maintenance?.message,
maintenanceEstimatedTime:
resourceData.maintenance?.["estimated-time"]
})
.where(
eq(resources.resourceId, existingResource.resourceId)
@@ -303,8 +322,13 @@ export async function updateProxyResources(
const headerAuthPassword =
resourceData.auth?.["basic-auth"]?.password;
const headerAuthExtendedCompatibility =
resourceData.auth?.["basic-auth"]?.extendedCompatibility;
if (headerAuthUser && headerAuthPassword && headerAuthExtendedCompatibility !== null) {
resourceData.auth?.["basic-auth"]
?.extendedCompatibility;
if (
headerAuthUser &&
headerAuthPassword &&
headerAuthExtendedCompatibility !== null
) {
const headerAuthHash = await hashPassword(
Buffer.from(
`${headerAuthUser}:${headerAuthPassword}`
@@ -315,10 +339,13 @@ export async function updateProxyResources(
resourceId: existingResource.resourceId,
headerAuthHash
}),
trx.insert(resourceHeaderAuthExtendedCompatibility).values({
resourceId: existingResource.resourceId,
extendedCompatibilityIsActivated: headerAuthExtendedCompatibility
})
trx
.insert(resourceHeaderAuthExtendedCompatibility)
.values({
resourceId: existingResource.resourceId,
extendedCompatibilityIsActivated:
headerAuthExtendedCompatibility
})
]);
}
}
@@ -380,7 +407,7 @@ export async function updateProxyResources(
if (targetSiteId) {
// Look up site by niceId
[site] = await trx
.select({siteId: sites.siteId})
.select({ siteId: sites.siteId })
.from(sites)
.where(
and(
@@ -392,7 +419,7 @@ export async function updateProxyResources(
} else if (siteId) {
// Use the provided siteId directly, but verify it belongs to the org
[site] = await trx
.select({siteId: sites.siteId})
.select({ siteId: sites.siteId })
.from(sites)
.where(
and(
@@ -437,7 +464,7 @@ export async function updateProxyResources(
if (checkIfTargetChanged(existingTarget, updatedTarget)) {
let internalPortToUpdate;
if (!targetData["internal-port"]) {
const {internalPort, targetIps} = await pickPort(
const { internalPort, targetIps } = await pickPort(
site.siteId!,
trx
);
@@ -622,6 +649,15 @@ export async function updateProxyResources(
);
}
const isLicensed = await isLicensedOrSubscribed(orgId);
if (build == "enterprise" && !isLicensed) {
logger.warn(
"Server is not licensed! Clearing set maintenance screen values"
);
// null the maintenance mode fields if not licensed
resourceData.maintenance = undefined;
}
// Create new resource
const [newResource] = await trx
.insert(resources)
@@ -643,7 +679,13 @@ export async function updateProxyResources(
ssl: resourceSsl,
headers: headers || null,
applyRules:
resourceData.rules && resourceData.rules.length > 0
resourceData.rules && resourceData.rules.length > 0,
maintenanceModeEnabled: resourceData.maintenance?.enabled,
maintenanceModeType: resourceData.maintenance?.type,
maintenanceTitle: resourceData.maintenance?.title,
maintenanceMessage: resourceData.maintenance?.message,
maintenanceEstimatedTime:
resourceData.maintenance?.["estimated-time"]
})
.returning();
@@ -674,9 +716,14 @@ export async function updateProxyResources(
const headerAuthUser = resourceData.auth?.["basic-auth"]?.user;
const headerAuthPassword =
resourceData.auth?.["basic-auth"]?.password;
const headerAuthExtendedCompatibility = resourceData.auth?.["basic-auth"]?.extendedCompatibility;
const headerAuthExtendedCompatibility =
resourceData.auth?.["basic-auth"]?.extendedCompatibility;
if (headerAuthUser && headerAuthPassword && headerAuthExtendedCompatibility !== null) {
if (
headerAuthUser &&
headerAuthPassword &&
headerAuthExtendedCompatibility !== null
) {
const headerAuthHash = await hashPassword(
Buffer.from(
`${headerAuthUser}:${headerAuthPassword}`
@@ -688,10 +735,13 @@ export async function updateProxyResources(
resourceId: newResource.resourceId,
headerAuthHash
}),
trx.insert(resourceHeaderAuthExtendedCompatibility).values({
resourceId: newResource.resourceId,
extendedCompatibilityIsActivated: headerAuthExtendedCompatibility
}),
trx
.insert(resourceHeaderAuthExtendedCompatibility)
.values({
resourceId: newResource.resourceId,
extendedCompatibilityIsActivated:
headerAuthExtendedCompatibility
})
]);
}
}
@@ -1043,7 +1093,7 @@ async function getDomain(
trx: Transaction
) {
const [fullDomainExists] = await trx
.select({resourceId: resources.resourceId})
.select({ resourceId: resources.resourceId })
.from(resources)
.where(
and(

View File

@@ -1,5 +1,6 @@
import { z } from "zod";
import { portRangeStringSchema } from "@server/lib/ip";
import { MaintenanceSchema } from "#dynamic/lib/blueprints/MaintenanceSchema";
export const SiteSchema = z.object({
name: z.string().min(1).max(100),
@@ -53,11 +54,13 @@ export const AuthSchema = z.object({
// pincode has to have 6 digits
pincode: z.number().min(100000).max(999999).optional(),
password: z.string().min(1).optional(),
"basic-auth": z.object({
user: z.string().min(1),
password: z.string().min(1),
extendedCompatibility: z.boolean().default(true)
}).optional(),
"basic-auth": z
.object({
user: z.string().min(1),
password: z.string().min(1),
extendedCompatibility: z.boolean().default(true)
})
.optional(),
"sso-enabled": z.boolean().optional().default(false),
"sso-roles": z
.array(z.string())
@@ -108,32 +111,30 @@ export const RuleSchema = z
.refine(
(rule) => {
if (rule.match === "country") {
// Check if it's a valid 2-letter country code
return /^[A-Z]{2}$/.test(rule.value);
// Check if it's a valid 2-letter country code or "ALL"
return /^[A-Z]{2}$/.test(rule.value) || rule.value === "ALL";
}
return true;
},
{
path: ["value"],
message:
"Value must be a 2-letter country code when match is 'country'"
"Value must be a 2-letter country code or 'ALL' when match is 'country'"
}
)
.refine(
(rule) => {
if (rule.match === "asn") {
// Check if it's either AS<number> format or just a number
// Check if it's either AS<number> format or "ALL"
const asNumberPattern = /^AS\d+$/i;
const isASFormat = asNumberPattern.test(rule.value);
const isNumeric = /^\d+$/.test(rule.value);
return isASFormat || isNumeric;
return asNumberPattern.test(rule.value) || rule.value === "ALL";
}
return true;
},
{
path: ["value"],
message:
"Value must be either 'AS<number>' format or a number when match is 'asn'"
"Value must be 'AS<number>' format or 'ALL' when match is 'asn'"
}
);
@@ -156,7 +157,8 @@ export const ResourceSchema = z
"host-header": z.string().optional(),
"tls-server-name": z.string().optional(),
headers: z.array(HeaderSchema).optional(),
rules: z.array(RuleSchema).optional()
rules: z.array(RuleSchema).optional(),
maintenance: MaintenanceSchema.optional()
})
.refine(
(resource) => {

View File

@@ -84,6 +84,10 @@ export class Config {
?.disable_basic_wireguard_sites
? "true"
: "false";
process.env.FLAGS_DISABLE_PRODUCT_HELP_BANNERS = parsedConfig.flags
?.disable_product_help_banners
? "true"
: "false";
process.env.PRODUCT_UPDATES_NOTIFICATION_ENABLED = parsedConfig.app
.notifications.product_updates

View File

@@ -4,6 +4,7 @@ import { and, eq, isNotNull } from "drizzle-orm";
import config from "@server/lib/config";
import z from "zod";
import logger from "@server/logger";
import semver from "semver";
interface IPRange {
start: bigint;
@@ -318,10 +319,7 @@ export function doCidrsOverlap(cidr1: string, cidr2: string): boolean {
const range2 = cidrToRange(cidr2);
// Overlap if the ranges intersect
return (
range1.start <= range2.end &&
range2.start <= range1.end
);
return range1.start <= range2.end && range2.start <= range1.end;
}
export async function getNextAvailableClientSubnet(
@@ -686,3 +684,35 @@ export function parsePortRangeString(
return result;
}
export function stripPortFromHost(ip: string, badgerVersion?: string): string {
const isNewerBadger =
badgerVersion &&
semver.valid(badgerVersion) &&
semver.gte(badgerVersion, "1.3.1");
if (isNewerBadger) {
return ip;
}
if (ip.startsWith("[") && ip.includes("]")) {
// if brackets are found, extract the IPv6 address from between the brackets
const ipv6Match = ip.match(/\[(.*?)\]/);
if (ipv6Match) {
return ipv6Match[1];
}
}
// Check if it looks like IPv4 (contains dots and matches IPv4 pattern)
// IPv4 format: x.x.x.x where x is 0-255
const ipv4Pattern = /^(\d{1,3}\.){3}\d{1,3}/;
if (ipv4Pattern.test(ip)) {
const lastColonIndex = ip.lastIndexOf(":");
if (lastColonIndex !== -1) {
return ip.substring(0, lastColonIndex);
}
}
// Return as is
return ip;
}

View File

@@ -216,7 +216,10 @@ export const configSchema = z
.default(["newt", "wireguard", "local"]),
allow_raw_resources: z.boolean().optional().default(true),
file_mode: z.boolean().optional().default(false),
pp_transport_prefix: z.string().optional().default("pp-transport-v")
pp_transport_prefix: z
.string()
.optional()
.default("pp-transport-v")
})
.optional()
.prefault({}),
@@ -327,7 +330,8 @@ export const configSchema = z
enable_integration_api: z.boolean().optional(),
disable_local_sites: z.boolean().optional(),
disable_basic_wireguard_sites: z.boolean().optional(),
disable_config_managed_domains: z.boolean().optional()
disable_config_managed_domains: z.boolean().optional(),
disable_product_help_banners: z.boolean().optional()
})
.optional(),
dns: z

View File

@@ -41,9 +41,10 @@ type TargetWithSite = Target & {
export async function getTraefikConfig(
exitNodeId: number,
siteTypes: string[],
filterOutNamespaceDomains = false,
generateLoginPageRouters = false,
allowRawResources = true
filterOutNamespaceDomains = false, // UNUSED BUT USED IN PRIVATE
generateLoginPageRouters = false, // UNUSED BUT USED IN PRIVATE
allowRawResources = true,
allowMaintenancePage = true, // UNUSED BUT USED IN PRIVATE
): Promise<any> {
// 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
@@ -294,12 +295,12 @@ export async function getTraefikConfig(
certResolver: resolverName,
...(preferWildcard
? {
domains: [
{
main: wildCard
}
]
}
domains: [
{
main: wildCard
}
]
}
: {})
};
@@ -475,9 +476,9 @@ export async function getTraefikConfig(
// RECEIVE BANDWIDTH ENDPOINT.
// TODO: HOW TO HANDLE ^^^^^^ BETTER
const anySitesOnline = (
targets
).some((target) => target.site.online);
const anySitesOnline = targets.some(
(target) => target.site.online
);
return (
targets
@@ -544,14 +545,14 @@ export async function getTraefikConfig(
})(),
...(resource.stickySession
? {
sticky: {
cookie: {
name: "p_sticky", // TODO: make this configurable via config.yml like other cookies
secure: resource.ssl,
httpOnly: true
}
}
}
sticky: {
cookie: {
name: "p_sticky", // TODO: make this configurable via config.yml like other cookies
secure: resource.ssl,
httpOnly: true
}
}
}
: {})
}
};
@@ -603,9 +604,9 @@ export async function getTraefikConfig(
loadBalancer: {
servers: (() => {
// Check if any sites are online
const anySitesOnline = (
targets
).some((target) => target.site.online);
const anySitesOnline = targets.some(
(target) => target.site.online
);
return targets
.filter((target) => {
@@ -654,18 +655,18 @@ export async function getTraefikConfig(
})(),
...(resource.proxyProtocol && protocol == "tcp"
? {
serversTransport: `${ppPrefix}${resource.proxyProtocolVersion || 1}@file` // TODO: does @file here cause issues?
}
serversTransport: `${ppPrefix}${resource.proxyProtocolVersion || 1}@file` // TODO: does @file here cause issues?
}
: {}),
...(resource.stickySession
? {
sticky: {
ipStrategy: {
depth: 0,
sourcePort: true
}
}
}
sticky: {
ipStrategy: {
depth: 0,
sourcePort: true
}
}
}
: {})
}
};

View File

@@ -0,0 +1,22 @@
/*
* This file is part of a proprietary work.
*
* Copyright (c) 2025 Fossorial, Inc.
* All rights reserved.
*
* This file is licensed under the Fossorial Commercial License.
* You may not use this file except in compliance with the License.
* Unauthorized use, copying, modification, or distribution is strictly prohibited.
*
* This file is not licensed under the AGPLv3.
*/
import { z } from "zod";
export const MaintenanceSchema = z.object({
enabled: z.boolean().optional(),
type: z.enum(["forced", "automatic"]).optional(),
title: z.string().max(255).nullable().optional(),
message: z.string().max(2000).nullable().optional(),
"estimated-time": z.string().max(100).nullable().optional()
});

View File

@@ -23,10 +23,10 @@ import {
} from "@server/lib/checkOrgAccessPolicy";
import { UserType } from "@server/types/UserTypes";
export async function enforceResourceSessionLength(
export function enforceResourceSessionLength(
resourceSession: ResourceSession,
org: Org
): Promise<{ valid: boolean; error?: string }> {
): { valid: boolean; error?: string } {
if (org.maxSessionLengthHours) {
const sessionIssuedAt = resourceSession.issuedAt; // may be null
const maxSessionLengthHours = org.maxSessionLengthHours;

View File

@@ -17,6 +17,7 @@ import logger from "@server/logger";
import { and, eq, lt } from "drizzle-orm";
import cache from "@server/lib/cache";
import { calculateCutoffTimestamp } from "@server/lib/cleanupLogs";
import { stripPortFromHost } from "@server/lib/ip";
async function getAccessDays(orgId: string): Promise<number> {
// check cache first
@@ -116,19 +117,7 @@ export async function logAccessAudit(data: {
}
const clientIp = data.requestIp
? (() => {
if (
data.requestIp.startsWith("[") &&
data.requestIp.includes("]")
) {
// if brackets are found, extract the IPv6 address from between the brackets
const ipv6Match = data.requestIp.match(/\[(.*?)\]/);
if (ipv6Match) {
return ipv6Match[1];
}
}
return data.requestIp;
})()
? stripPortFromHost(data.requestIp)
: undefined;
const countryCode = data.requestIp

View File

@@ -71,9 +71,9 @@ export async function getTraefikConfig(
siteTypes: string[],
filterOutNamespaceDomains = false,
generateLoginPageRouters = false,
allowRawResources = true
allowRawResources = true,
allowMaintenancePage = true
): Promise<any> {
// 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
const resourcesWithTargetsAndSites = await db
@@ -358,18 +358,6 @@ export async function getTraefikConfig(
}
}
if (resource.ssl) {
config_output.http.routers![routerName + "-redirect"] = {
entryPoints: [
config.getRawConfig().traefik.http_entrypoint
],
middlewares: [redirectHttpsMiddlewareName],
service: serviceName,
rule: rule,
priority: priority
};
}
let tls = {};
if (!privateConfig.getRawPrivateConfig().flags.use_pangolin_dns) {
const domainParts = fullDomain.split(".");
@@ -435,17 +423,27 @@ export async function getTraefikConfig(
}
}
const availableServers = targets.filter(
(target) => {
if (!target.enabled) return false;
if (resource.ssl) {
config_output.http.routers![routerName + "-redirect"] = {
entryPoints: [
config.getRawConfig().traefik.http_entrypoint
],
middlewares: [redirectHttpsMiddlewareName],
service: serviceName,
rule: rule,
priority: priority
};
}
if (!target.site.online) return false;
const availableServers = targets.filter((target) => {
if (!target.enabled) return false;
if (target.health == "unhealthy") return false;
if (!target.site.online) return false;
return true;
}
);
if (target.health == "unhealthy") return false;
return true;
});
const hasHealthyServers = availableServers.length > 0;
@@ -466,7 +464,7 @@ export async function getTraefikConfig(
}
}
if (showMaintenancePage) {
if (showMaintenancePage && allowMaintenancePage) {
const maintenanceServiceName = `${key}-maintenance-service`;
const maintenanceRouterName = `${key}-maintenance-router`;
const rewriteMiddlewareName = `${key}-maintenance-rewrite`;
@@ -794,9 +792,9 @@ export async function getTraefikConfig(
loadBalancer: {
servers: (() => {
// Check if any sites are online
const anySitesOnline = (
targets
).some((target) => target.site.online);
const anySitesOnline = targets.some(
(target) => target.site.online
);
return targets
.filter((target) => {

View File

@@ -40,6 +40,7 @@ import {
ResourceHeaderAuthExtendedCompatibility,
orgs,
requestAuditLog,
Org
} from "@server/db";
import {
resources,
@@ -79,6 +80,7 @@ import { maxmindLookup } from "@server/db/maxmind";
import { verifyResourceAccessToken } from "@server/auth/verifyResourceAccessToken";
import semver from "semver";
import { maxmindAsnLookup } from "@server/db/maxmindAsn";
import { checkOrgAccessPolicy } from "@server/lib/checkOrgAccessPolicy";
// Zod schemas for request validation
const getResourceByDomainParamsSchema = z.strictObject({
@@ -94,6 +96,12 @@ const getUserOrgRoleParamsSchema = z.strictObject({
orgId: z.string().min(1, "Organization ID is required")
});
const getUserOrgSessionVerifySchema = z.strictObject({
userId: z.string().min(1, "User ID is required"),
orgId: z.string().min(1, "Organization ID is required"),
sessionId: z.string().min(1, "Session ID is required")
});
const getRoleResourceAccessParamsSchema = z.strictObject({
roleId: z
.string()
@@ -178,6 +186,7 @@ export type ResourceWithAuth = {
password: ResourcePassword | null;
headerAuth: ResourceHeaderAuth | null;
headerAuthExtendedCompatibility: ResourceHeaderAuthExtendedCompatibility | null;
org: Org
};
export type UserSessionWithUser = {
@@ -238,7 +247,8 @@ hybridRouter.get(
["newt", "local", "wireguard"], // Allow them to use all the site types
true, // But don't allow domain namespace resources
false, // Dont include login pages,
true // allow raw resources
true, // allow raw resources
false // dont generate maintenance page
);
return response(res, {
@@ -503,8 +513,12 @@ hybridRouter.get(
)
.leftJoin(
resourceHeaderAuthExtendedCompatibility,
eq(resourceHeaderAuthExtendedCompatibility.resourceId, resources.resourceId)
eq(
resourceHeaderAuthExtendedCompatibility.resourceId,
resources.resourceId
)
)
.innerJoin(orgs, eq(orgs.orgId, resources.orgId))
.where(eq(resources.fullDomain, domain))
.limit(1);
@@ -538,7 +552,9 @@ hybridRouter.get(
pincode: result.resourcePincode,
password: result.resourcePassword,
headerAuth: result.resourceHeaderAuth,
headerAuthExtendedCompatibility: result.resourceHeaderAuthExtendedCompatibility
headerAuthExtendedCompatibility:
result.resourceHeaderAuthExtendedCompatibility,
org: result.orgs
};
return response<ResourceWithAuth>(res, {
@@ -602,6 +618,16 @@ hybridRouter.get(
)
.limit(1);
if (!result) {
return response<LoginPage | null>(res, {
data: null,
success: true,
error: false,
message: "Login page not found",
status: HttpCode.OK
});
}
if (
await checkExitNodeOrg(
remoteExitNode.exitNodeId,
@@ -617,16 +643,6 @@ hybridRouter.get(
);
}
if (!result) {
return response<LoginPage | null>(res, {
data: null,
success: true,
error: false,
message: "Login page not found",
status: HttpCode.OK
});
}
return response<LoginPage>(res, {
data: result.loginPage,
success: true,
@@ -818,6 +834,69 @@ hybridRouter.get(
}
);
// Get user organization role
hybridRouter.get(
"/user/:userId/org/:orgId/session/:sessionId/verify",
async (req: Request, res: Response, next: NextFunction) => {
try {
const parsedParams = getUserOrgSessionVerifySchema.safeParse(
req.params
);
if (!parsedParams.success) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
fromError(parsedParams.error).toString()
)
);
}
const { userId, orgId, sessionId } = parsedParams.data;
const remoteExitNode = req.remoteExitNode;
if (!remoteExitNode || !remoteExitNode.exitNodeId) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
"Remote exit node not found"
)
);
}
if (await checkExitNodeOrg(remoteExitNode.exitNodeId, orgId)) {
return next(
createHttpError(
HttpCode.UNAUTHORIZED,
"User is not authorized to access this organization"
)
);
}
const accessPolicy = await checkOrgAccessPolicy({
orgId,
userId,
sessionId
});
return response(res, {
data: accessPolicy,
success: true,
error: false,
message: "User org access policy retrieved successfully",
status: HttpCode.OK
});
} catch (error) {
logger.error(error);
return next(
createHttpError(
HttpCode.INTERNAL_SERVER_ERROR,
"Failed to get user org role"
)
);
}
}
);
// Check if role has access to resource
hybridRouter.get(
"/role/:roleId/resource/:resourceId/access",

View File

@@ -39,4 +39,4 @@ internalRouter.post(
internalRouter.get(`/license/status`, license.getLicenseStatus);
internalRouter.get("/maintenance/info", resource.getMaintenanceInfo);
internalRouter.get("/maintenance/info", resource.getMaintenanceInfo);

View File

@@ -40,6 +40,11 @@ async function query(orgId: string | undefined, fullDomain: string) {
eq(loginPage.loginPageId, loginPageOrg.loginPageId)
)
.limit(1);
if (!res) {
return null;
}
return {
...res.loginPage,
orgId: res.loginPageOrg.orgId
@@ -65,6 +70,11 @@ async function query(orgId: string | undefined, fullDomain: string) {
)
)
.limit(1);
if (!res) {
return null;
}
return {
...res,
orgId: orgLink.orgId

View File

@@ -48,6 +48,11 @@ async function query(orgId: string) {
)
)
.limit(1);
if (!res) {
return null;
}
return {
...res,
orgId: orgLink.orgs.orgId,

View File

@@ -11,4 +11,4 @@
* This file is not licensed under the AGPLv3.
*/
export * from "./getMaintenanceInfo";
export * from "./getMaintenanceInfo";

View File

@@ -99,12 +99,13 @@ async function query(query: Q) {
.where(and(baseConditions, not(isNull(requestAuditLog.location))))
.groupBy(requestAuditLog.location)
.orderBy(desc(totalQ))
.limit(DISTINCT_LIMIT+1);
.limit(DISTINCT_LIMIT + 1);
if (requestsPerCountry.length > DISTINCT_LIMIT) {
// throw an error
throw createHttpError(
HttpCode.BAD_REQUEST,
// todo: is this even possible?
`Too many distinct countries. Please narrow your query.`
);
}

View File

@@ -189,22 +189,22 @@ async function queryUniqueFilterAttributes(
.selectDistinct({ actor: requestAuditLog.actor })
.from(requestAuditLog)
.where(baseConditions)
.limit(DISTINCT_LIMIT+1),
.limit(DISTINCT_LIMIT + 1),
primaryDb
.selectDistinct({ locations: requestAuditLog.location })
.from(requestAuditLog)
.where(baseConditions)
.limit(DISTINCT_LIMIT+1),
.limit(DISTINCT_LIMIT + 1),
primaryDb
.selectDistinct({ hosts: requestAuditLog.host })
.from(requestAuditLog)
.where(baseConditions)
.limit(DISTINCT_LIMIT+1),
.limit(DISTINCT_LIMIT + 1),
primaryDb
.selectDistinct({ paths: requestAuditLog.path })
.from(requestAuditLog)
.where(baseConditions)
.limit(DISTINCT_LIMIT+1),
.limit(DISTINCT_LIMIT + 1),
primaryDb
.selectDistinct({
id: requestAuditLog.resourceId,
@@ -216,18 +216,20 @@ async function queryUniqueFilterAttributes(
eq(requestAuditLog.resourceId, resources.resourceId)
)
.where(baseConditions)
.limit(DISTINCT_LIMIT+1)
.limit(DISTINCT_LIMIT + 1)
]);
if (
uniqueActors.length > DISTINCT_LIMIT ||
uniqueLocations.length > DISTINCT_LIMIT ||
uniqueHosts.length > DISTINCT_LIMIT ||
uniquePaths.length > DISTINCT_LIMIT ||
uniqueResources.length > DISTINCT_LIMIT
) {
throw new Error("Too many distinct filter attributes to retrieve. Please refine your time range.");
}
// TODO: for stuff like the paths this is too restrictive so lets just show some of the paths and the user needs to
// refine the time range to see what they need to see
// if (
// uniqueActors.length > DISTINCT_LIMIT ||
// uniqueLocations.length > DISTINCT_LIMIT ||
// uniqueHosts.length > DISTINCT_LIMIT ||
// uniquePaths.length > DISTINCT_LIMIT ||
// uniqueResources.length > DISTINCT_LIMIT
// ) {
// throw new Error("Too many distinct filter attributes to retrieve. Please refine your time range.");
// }
return {
actors: uniqueActors
@@ -307,10 +309,12 @@ export async function queryRequestAuditLogs(
} catch (error) {
logger.error(error);
// if the message is "Too many distinct filter attributes to retrieve. Please refine your time range.", return a 400 and the message
if (error instanceof Error && error.message === "Too many distinct filter attributes to retrieve. Please refine your time range.") {
return next(
createHttpError(HttpCode.BAD_REQUEST, error.message)
);
if (
error instanceof Error &&
error.message ===
"Too many distinct filter attributes to retrieve. Please refine your time range."
) {
return next(createHttpError(HttpCode.BAD_REQUEST, error.message));
}
return next(
createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred")

View File

@@ -10,6 +10,7 @@ import { eq, and, gt } from "drizzle-orm";
import { createSession, generateSessionToken } from "@server/auth/sessions/app";
import { encodeHexLowerCase } from "@oslojs/encoding";
import { sha256 } from "@oslojs/crypto/sha2";
import { stripPortFromHost } from "@server/lib/ip";
const paramsSchema = z.object({
code: z.string().min(1, "Code is required")
@@ -27,30 +28,6 @@ export type PollDeviceWebAuthResponse = {
token?: string;
};
// Helper function to extract IP from request (same as in startDeviceWebAuth)
function extractIpFromRequest(req: Request): string | undefined {
const ip = req.ip || req.socket.remoteAddress;
if (!ip) {
return undefined;
}
// Handle IPv6 format [::1] or IPv4 format
if (ip.startsWith("[") && ip.includes("]")) {
const ipv6Match = ip.match(/\[(.*?)\]/);
if (ipv6Match) {
return ipv6Match[1];
}
}
// Handle IPv4 with port (split at last colon)
const lastColonIndex = ip.lastIndexOf(":");
if (lastColonIndex !== -1) {
return ip.substring(0, lastColonIndex);
}
return ip;
}
export async function pollDeviceWebAuth(
req: Request,
res: Response,
@@ -70,7 +47,7 @@ export async function pollDeviceWebAuth(
try {
const { code } = parsedParams.data;
const now = Date.now();
const requestIp = extractIpFromRequest(req);
const requestIp = req.ip ? stripPortFromHost(req.ip) : undefined;
// Hash the code before querying
const hashedCode = hashDeviceCode(code);

View File

@@ -12,6 +12,7 @@ import { TimeSpan } from "oslo";
import { maxmindLookup } from "@server/db/maxmind";
import { encodeHexLowerCase } from "@oslojs/encoding";
import { sha256 } from "@oslojs/crypto/sha2";
import { stripPortFromHost } from "@server/lib/ip";
const bodySchema = z
.object({
@@ -39,30 +40,6 @@ function hashDeviceCode(code: string): string {
return encodeHexLowerCase(sha256(new TextEncoder().encode(code)));
}
// Helper function to extract IP from request
function extractIpFromRequest(req: Request): string | undefined {
const ip = req.ip;
if (!ip) {
return undefined;
}
// Handle IPv6 format [::1] or IPv4 format
if (ip.startsWith("[") && ip.includes("]")) {
const ipv6Match = ip.match(/\[(.*?)\]/);
if (ipv6Match) {
return ipv6Match[1];
}
}
// Handle IPv4 with port (split at last colon)
const lastColonIndex = ip.lastIndexOf(":");
if (lastColonIndex !== -1) {
return ip.substring(0, lastColonIndex);
}
return ip;
}
// Helper function to get city from IP (if available)
async function getCityFromIp(ip: string): Promise<string | undefined> {
try {
@@ -112,7 +89,7 @@ export async function startDeviceWebAuth(
const hashedCode = hashDeviceCode(code);
// Extract IP from request
const ip = extractIpFromRequest(req);
const ip = req.ip ? stripPortFromHost(req.ip) : undefined;
// Get city (optional, may return undefined)
const city = ip ? await getCityFromIp(ip) : undefined;

View File

@@ -19,6 +19,7 @@ import {
import { SESSION_COOKIE_EXPIRES as RESOURCE_SESSION_COOKIE_EXPIRES } from "@server/auth/sessions/resource";
import config from "@server/lib/config";
import { response } from "@server/lib/response";
import { stripPortFromHost } from "@server/lib/ip";
const exchangeSessionBodySchema = z.object({
requestToken: z.string(),
@@ -62,7 +63,7 @@ export async function exchangeSession(
cleanHost = cleanHost.slice(0, -1 * matched.length);
}
const clientIp = requestIp?.split(":")[0];
const clientIp = requestIp ? stripPortFromHost(requestIp) : undefined;
const [resource] = await db
.select()

View File

@@ -3,6 +3,7 @@ import logger from "@server/logger";
import { and, eq, lt } from "drizzle-orm";
import cache from "@server/lib/cache";
import { calculateCutoffTimestamp } from "@server/lib/cleanupLogs";
import { stripPortFromHost } from "@server/lib/ip";
/**
@@ -208,26 +209,7 @@ export async function logRequestAudit(
}
const clientIp = body.requestIp
? (() => {
if (
body.requestIp.startsWith("[") &&
body.requestIp.includes("]")
) {
// if brackets are found, extract the IPv6 address from between the brackets
const ipv6Match = body.requestIp.match(/\[(.*?)\]/);
if (ipv6Match) {
return ipv6Match[1];
}
}
// ivp4
// split at last colon
const lastColonIndex = body.requestIp.lastIndexOf(":");
if (lastColonIndex !== -1) {
return body.requestIp.substring(0, lastColonIndex);
}
return body.requestIp;
})()
? stripPortFromHost(body.requestIp)
: undefined;
// Add to buffer instead of writing directly to DB

View File

@@ -13,14 +13,15 @@ import {
LoginPage,
Org,
Resource,
ResourceHeaderAuth, ResourceHeaderAuthExtendedCompatibility,
ResourceHeaderAuth,
ResourceHeaderAuthExtendedCompatibility,
ResourcePassword,
ResourcePincode,
ResourceRule,
resourceSessions
} from "@server/db";
import config from "@server/lib/config";
import { isIpInCidr } from "@server/lib/ip";
import { isIpInCidr, stripPortFromHost } from "@server/lib/ip";
import { response } from "@server/lib/response";
import logger from "@server/logger";
import HttpCode from "@server/types/HttpCode";
@@ -39,6 +40,8 @@ import {
} from "#dynamic/lib/checkOrgAccessPolicy";
import { logRequestAudit } from "./logRequestAudit";
import cache from "@server/lib/cache";
import semver from "semver";
import { APP_VERSION } from "@server/lib/consts";
const verifyResourceSessionSchema = z.object({
sessions: z.record(z.string(), z.string()).optional(),
@@ -50,7 +53,8 @@ const verifyResourceSessionSchema = z.object({
path: z.string(),
method: z.string(),
tls: z.boolean(),
requestIp: z.string().optional()
requestIp: z.string().optional(),
badgerVersion: z.string().optional()
});
export type VerifyResourceSessionSchema = z.infer<
@@ -69,6 +73,7 @@ export type VerifyUserResponse = {
headerAuthChallenged?: boolean;
redirectUrl?: string;
userData?: BasicUserData;
pangolinVersion?: string;
};
export async function verifyResourceSession(
@@ -97,31 +102,15 @@ export async function verifyResourceSession(
requestIp,
path,
headers,
query
query,
badgerVersion
} = parsedBody.data;
// Extract HTTP Basic Auth credentials if present
const clientHeaderAuth = extractBasicAuth(headers);
const clientIp = requestIp
? (() => {
logger.debug("Request IP:", { requestIp });
if (requestIp.startsWith("[") && requestIp.includes("]")) {
// if brackets are found, extract the IPv6 address from between the brackets
const ipv6Match = requestIp.match(/\[(.*?)\]/);
if (ipv6Match) {
return ipv6Match[1];
}
}
// ivp4
// split at last colon
const lastColonIndex = requestIp.lastIndexOf(":");
if (lastColonIndex !== -1) {
return requestIp.substring(0, lastColonIndex);
}
return requestIp;
})()
? stripPortFromHost(requestIp, badgerVersion)
: undefined;
logger.debug("Client IP:", { clientIp });
@@ -130,9 +119,7 @@ export async function verifyResourceSession(
? await getCountryCodeFromIp(clientIp)
: undefined;
const ipAsn = clientIp
? await getAsnFromIp(clientIp)
: undefined;
const ipAsn = clientIp ? await getAsnFromIp(clientIp) : undefined;
let cleanHost = host;
// if the host ends with :port, strip it
@@ -178,7 +165,13 @@ export async function verifyResourceSession(
cache.set(resourceCacheKey, resourceData, 5);
}
const { resource, pincode, password, headerAuth, headerAuthExtendedCompatibility } = resourceData;
const {
resource,
pincode,
password,
headerAuth,
headerAuthExtendedCompatibility
} = resourceData;
if (!resource) {
logger.debug(`Resource not found ${cleanHost}`);
@@ -474,8 +467,7 @@ export async function verifyResourceSession(
return notAllowed(res);
}
}
else if (headerAuth) {
} else if (headerAuth) {
// if there are no other auth methods we need to return unauthorized if nothing is provided
if (
!sso &&
@@ -713,7 +705,11 @@ export async function verifyResourceSession(
}
// If headerAuthExtendedCompatibility is activated but no clientHeaderAuth provided, force client to challenge
if (headerAuthExtendedCompatibility && headerAuthExtendedCompatibility.extendedCompatibilityIsActivated && !clientHeaderAuth){
if (
headerAuthExtendedCompatibility &&
headerAuthExtendedCompatibility.extendedCompatibilityIsActivated &&
!clientHeaderAuth
) {
return headerAuthChallenged(res, redirectPath, resource.orgId);
}
@@ -825,7 +821,7 @@ async function notAllowed(
}
const data = {
data: { valid: false, redirectUrl },
data: { valid: false, redirectUrl, pangolinVersion: APP_VERSION },
success: true,
error: false,
message: "Access denied",
@@ -839,8 +835,8 @@ function allowed(res: Response, userData?: BasicUserData) {
const data = {
data:
userData !== undefined && userData !== null
? { valid: true, ...userData }
: { valid: true },
? { valid: true, ...userData, pangolinVersion: APP_VERSION }
: { valid: true, pangolinVersion: APP_VERSION },
success: true,
error: false,
message: "Access allowed",
@@ -879,7 +875,12 @@ async function headerAuthChallenged(
}
const data = {
data: { headerAuthChallenged: true, valid: false, redirectUrl },
data: {
headerAuthChallenged: true,
valid: false,
redirectUrl,
pangolinVersion: APP_VERSION
},
success: true,
error: false,
message: "Access denied",

View File

@@ -36,7 +36,7 @@ async function query(clientId?: number, niceId?: string, orgId?: string) {
.select()
.from(clients)
.where(and(eq(clients.niceId, niceId), eq(clients.orgId, orgId)))
.leftJoin(olms, eq(olms.clientId, olms.clientId))
.leftJoin(olms, eq(clients.clientId, olms.clientId))
.limit(1);
return res;
}

View File

@@ -56,12 +56,12 @@ async function getLatestOlmVersion(): Promise<string | null> {
return null;
}
const tags = await response.json();
let tags = await response.json();
if (!Array.isArray(tags) || tags.length === 0) {
logger.warn("No tags found for Olm repository");
return null;
}
tags = tags.filter((version) => !version.name.includes("rc"));
const latestVersion = tags[0].name;
olmVersionCache.set("latestOlmVersion", latestVersion);

View File

@@ -52,7 +52,7 @@ export async function getConfig(
}
// clean up the public key - keep only valid base64 characters (A-Z, a-z, 0-9, +, /, =)
const cleanedPublicKey = publicKey.replace(/[^A-Za-z0-9+/=]/g, '');
const cleanedPublicKey = publicKey.replace(/[^A-Za-z0-9+/=]/g, "");
const exitNode = await createExitNode(cleanedPublicKey, reachableAt);

View File

@@ -858,7 +858,6 @@ authenticated.put(
blueprints.applyJSONBlueprint
);
authenticated.get(
"/org/:orgId/blueprint/:blueprintId",
verifyApiKeyOrgAccess,
@@ -866,7 +865,6 @@ authenticated.get(
blueprints.getBlueprint
);
authenticated.get(
"/org/:orgId/blueprints",
verifyApiKeyOrgAccess,

View File

@@ -1,7 +1,7 @@
import { db } from "@server/db";
import { MessageHandler } from "@server/routers/ws";
import { clients, Newt } from "@server/db";
import { eq } from "drizzle-orm";
import { clients } from "@server/db";
import { eq, sql } from "drizzle-orm";
import logger from "@server/logger";
interface PeerBandwidth {
@@ -10,13 +10,57 @@ interface PeerBandwidth {
bytesOut: number;
}
// Retry configuration for deadlock handling
const MAX_RETRIES = 3;
const BASE_DELAY_MS = 50;
/**
* Check if an error is a deadlock error
*/
function isDeadlockError(error: any): boolean {
return (
error?.code === "40P01" ||
error?.cause?.code === "40P01" ||
(error?.message && error.message.includes("deadlock"))
);
}
/**
* Execute a function with retry logic for deadlock handling
*/
async function withDeadlockRetry<T>(
operation: () => Promise<T>,
context: string
): Promise<T> {
let attempt = 0;
while (true) {
try {
return await operation();
} catch (error: any) {
if (isDeadlockError(error) && attempt < MAX_RETRIES) {
attempt++;
const baseDelay = Math.pow(2, attempt - 1) * BASE_DELAY_MS;
const jitter = Math.random() * baseDelay;
const delay = baseDelay + jitter;
logger.warn(
`Deadlock detected in ${context}, retrying attempt ${attempt}/${MAX_RETRIES} after ${delay.toFixed(0)}ms`
);
await new Promise((resolve) => setTimeout(resolve, delay));
continue;
}
throw error;
}
}
}
export const handleReceiveBandwidthMessage: MessageHandler = async (
context
) => {
const { message, client, sendToClient } = context;
const { message } = context;
if (!message.data.bandwidthData) {
logger.warn("No bandwidth data provided");
return;
}
const bandwidthData: PeerBandwidth[] = message.data.bandwidthData;
@@ -25,30 +69,40 @@ export const handleReceiveBandwidthMessage: MessageHandler = async (
throw new Error("Invalid bandwidth data");
}
await db.transaction(async (trx) => {
for (const peer of bandwidthData) {
const { publicKey, bytesIn, bytesOut } = peer;
// Sort bandwidth data by publicKey to ensure consistent lock ordering across all instances
// This is critical for preventing deadlocks when multiple instances update the same clients
const sortedBandwidthData = [...bandwidthData].sort((a, b) =>
a.publicKey.localeCompare(b.publicKey)
);
// Find the client by public key
const [client] = await trx
.select()
.from(clients)
.where(eq(clients.pubKey, publicKey))
.limit(1);
const currentTime = new Date().toISOString();
if (!client) {
continue;
}
// Update each client individually with retry logic
// This reduces transaction scope and allows retries per-client
for (const peer of sortedBandwidthData) {
const { publicKey, bytesIn, bytesOut } = peer;
// Update the client's bandwidth usage
await trx
.update(clients)
.set({
megabytesOut: (client.megabytesIn || 0) + bytesIn,
megabytesIn: (client.megabytesOut || 0) + bytesOut,
lastBandwidthUpdate: new Date().toISOString()
})
.where(eq(clients.clientId, client.clientId));
try {
await withDeadlockRetry(async () => {
// Use atomic SQL increment to avoid SELECT then UPDATE pattern
// This eliminates the need to read the current value first
await db
.update(clients)
.set({
// Note: bytesIn from peer goes to megabytesOut (data sent to client)
// and bytesOut from peer goes to megabytesIn (data received from client)
megabytesOut: sql`COALESCE(${clients.megabytesOut}, 0) + ${bytesIn}`,
megabytesIn: sql`COALESCE(${clients.megabytesIn}, 0) + ${bytesOut}`,
lastBandwidthUpdate: currentTime
})
.where(eq(clients.pubKey, publicKey));
}, `update client bandwidth ${publicKey}`);
} catch (error) {
logger.error(
`Failed to update bandwidth for client ${publicKey}:`,
error
);
// Continue with other clients even if one fails
}
});
}
};

View File

@@ -21,8 +21,7 @@ export async function pickOrgDefaults(
// const subnet = await getNextAvailableOrgSubnet();
// Just hard code the subnet for now for everyone
const subnet = config.getRawConfig().orgs.subnet_group;
const utilitySubnet =
config.getRawConfig().orgs.utility_subnet_group;
const utilitySubnet = config.getRawConfig().orgs.utility_subnet_group;
return response<PickOrgDefaultsResponse>(res, {
data: {

View File

@@ -154,4 +154,4 @@ export async function updateOrg(
createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred")
);
}
}
}

View File

@@ -1,19 +1,20 @@
import {Request, Response, NextFunction} from "express";
import {z} from "zod";
import { Request, Response, NextFunction } from "express";
import { z } from "zod";
import {
db,
resourceHeaderAuth, resourceHeaderAuthExtendedCompatibility,
resourceHeaderAuth,
resourceHeaderAuthExtendedCompatibility,
resourcePassword,
resourcePincode,
resources
} from "@server/db";
import {eq} from "drizzle-orm";
import { eq } from "drizzle-orm";
import response from "@server/lib/response";
import HttpCode from "@server/types/HttpCode";
import createHttpError from "http-errors";
import {fromError} from "zod-validation-error";
import { fromError } from "zod-validation-error";
import logger from "@server/logger";
import {build} from "@server/build";
import { build } from "@server/build";
const getResourceAuthInfoSchema = z.strictObject({
resourceGuid: z.string()
@@ -52,68 +53,68 @@ export async function getResourceAuthInfo(
);
}
const {resourceGuid} = parsedParams.data;
const { resourceGuid } = parsedParams.data;
const isGuidInteger = /^\d+$/.test(resourceGuid);
const [result] =
isGuidInteger && build === "saas"
? await db
.select()
.from(resources)
.leftJoin(
resourcePincode,
eq(resourcePincode.resourceId, resources.resourceId)
)
.leftJoin(
resourcePassword,
eq(resourcePassword.resourceId, resources.resourceId)
)
.select()
.from(resources)
.leftJoin(
resourcePincode,
eq(resourcePincode.resourceId, resources.resourceId)
)
.leftJoin(
resourcePassword,
eq(resourcePassword.resourceId, resources.resourceId)
)
.leftJoin(
resourceHeaderAuth,
eq(
resourceHeaderAuth.resourceId,
resources.resourceId
)
)
.leftJoin(
resourceHeaderAuthExtendedCompatibility,
eq(
resourceHeaderAuthExtendedCompatibility.resourceId,
resources.resourceId
)
)
.where(eq(resources.resourceId, Number(resourceGuid)))
.limit(1)
.leftJoin(
resourceHeaderAuth,
eq(
resourceHeaderAuth.resourceId,
resources.resourceId
)
)
.leftJoin(
resourceHeaderAuthExtendedCompatibility,
eq(
resourceHeaderAuthExtendedCompatibility.resourceId,
resources.resourceId
)
)
.where(eq(resources.resourceId, Number(resourceGuid)))
.limit(1)
: await db
.select()
.from(resources)
.leftJoin(
resourcePincode,
eq(resourcePincode.resourceId, resources.resourceId)
)
.leftJoin(
resourcePassword,
eq(resourcePassword.resourceId, resources.resourceId)
)
.select()
.from(resources)
.leftJoin(
resourcePincode,
eq(resourcePincode.resourceId, resources.resourceId)
)
.leftJoin(
resourcePassword,
eq(resourcePassword.resourceId, resources.resourceId)
)
.leftJoin(
resourceHeaderAuth,
eq(
resourceHeaderAuth.resourceId,
resources.resourceId
)
)
.leftJoin(
resourceHeaderAuthExtendedCompatibility,
eq(
resourceHeaderAuthExtendedCompatibility.resourceId,
resources.resourceId
)
)
.where(eq(resources.resourceGuid, resourceGuid))
.limit(1);
.leftJoin(
resourceHeaderAuth,
eq(
resourceHeaderAuth.resourceId,
resources.resourceId
)
)
.leftJoin(
resourceHeaderAuthExtendedCompatibility,
eq(
resourceHeaderAuthExtendedCompatibility.resourceId,
resources.resourceId
)
)
.where(eq(resources.resourceGuid, resourceGuid))
.limit(1);
const resource = result?.resources;
if (!resource) {
@@ -125,7 +126,8 @@ export async function getResourceAuthInfo(
const pincode = result?.resourcePincode;
const password = result?.resourcePassword;
const headerAuth = result?.resourceHeaderAuth;
const headerAuthExtendedCompatibility = result?.resourceHeaderAuthExtendedCompatibility;
const headerAuthExtendedCompatibility =
result?.resourceHeaderAuthExtendedCompatibility;
const url = `${resource.ssl ? "https" : "http"}://${resource.fullDomain}`;
@@ -138,7 +140,8 @@ export async function getResourceAuthInfo(
password: password !== null,
pincode: pincode !== null,
headerAuth: headerAuth !== null,
headerAuthExtendedCompatibility: headerAuthExtendedCompatibility !== null,
headerAuthExtendedCompatibility:
headerAuthExtendedCompatibility !== null,
sso: resource.sso,
blockAccess: resource.blockAccess,
url,

View File

@@ -1,6 +1,10 @@
import { Request, Response, NextFunction } from "express";
import { z } from "zod";
import {db, resourceHeaderAuth, resourceHeaderAuthExtendedCompatibility} from "@server/db";
import {
db,
resourceHeaderAuth,
resourceHeaderAuthExtendedCompatibility
} from "@server/db";
import {
resources,
userResources,
@@ -109,7 +113,8 @@ function queryResources(accessibleResourceIds: number[], orgId: string) {
domainId: resources.domainId,
niceId: resources.niceId,
headerAuthId: resourceHeaderAuth.headerAuthId,
headerAuthExtendedCompatibilityId: resourceHeaderAuthExtendedCompatibility.headerAuthExtendedCompatibilityId,
headerAuthExtendedCompatibilityId:
resourceHeaderAuthExtendedCompatibility.headerAuthExtendedCompatibilityId,
targetId: targets.targetId,
targetIp: targets.ip,
targetPort: targets.port,
@@ -133,7 +138,10 @@ function queryResources(accessibleResourceIds: number[], orgId: string) {
)
.leftJoin(
resourceHeaderAuthExtendedCompatibility,
eq(resourceHeaderAuthExtendedCompatibility.resourceId, resources.resourceId)
eq(
resourceHeaderAuthExtendedCompatibility.resourceId,
resources.resourceId
)
)
.leftJoin(targets, eq(targets.resourceId, resources.resourceId))
.leftJoin(

View File

@@ -1,14 +1,18 @@
import {Request, Response, NextFunction} from "express";
import {z} from "zod";
import {db, resourceHeaderAuth, resourceHeaderAuthExtendedCompatibility} from "@server/db";
import {eq} from "drizzle-orm";
import { Request, Response, NextFunction } from "express";
import { z } from "zod";
import {
db,
resourceHeaderAuth,
resourceHeaderAuthExtendedCompatibility
} from "@server/db";
import { eq } from "drizzle-orm";
import HttpCode from "@server/types/HttpCode";
import createHttpError from "http-errors";
import {fromError} from "zod-validation-error";
import {response} from "@server/lib/response";
import { fromError } from "zod-validation-error";
import { response } from "@server/lib/response";
import logger from "@server/logger";
import {hashPassword} from "@server/auth/password";
import {OpenAPITags, registry} from "@server/openApi";
import { hashPassword } from "@server/auth/password";
import { OpenAPITags, registry } from "@server/openApi";
const setResourceAuthMethodsParamsSchema = z.object({
resourceId: z.string().transform(Number).pipe(z.int().positive())
@@ -67,29 +71,40 @@ export async function setResourceHeaderAuth(
);
}
const {resourceId} = parsedParams.data;
const {user, password, extendedCompatibility} = parsedBody.data;
const { resourceId } = parsedParams.data;
const { user, password, extendedCompatibility } = parsedBody.data;
await db.transaction(async (trx) => {
await trx
.delete(resourceHeaderAuth)
.where(eq(resourceHeaderAuth.resourceId, resourceId));
await trx.delete(resourceHeaderAuthExtendedCompatibility).where(eq(resourceHeaderAuthExtendedCompatibility.resourceId, resourceId));
await trx
.delete(resourceHeaderAuthExtendedCompatibility)
.where(
eq(
resourceHeaderAuthExtendedCompatibility.resourceId,
resourceId
)
);
if (user && password && extendedCompatibility !== null) {
const headerAuthHash = await hashPassword(Buffer.from(`${user}:${password}`).toString("base64"));
const headerAuthHash = await hashPassword(
Buffer.from(`${user}:${password}`).toString("base64")
);
await Promise.all([
trx
.insert(resourceHeaderAuth)
.values({resourceId, headerAuthHash}),
.values({ resourceId, headerAuthHash }),
trx
.insert(resourceHeaderAuthExtendedCompatibility)
.values({resourceId, extendedCompatibilityIsActivated: extendedCompatibility})
.values({
resourceId,
extendedCompatibilityIsActivated:
extendedCompatibility
})
]);
}
});
return response(res, {

View File

@@ -7,4 +7,4 @@ export type GetMaintenanceInfoResponse = {
maintenanceTitle: string | null;
maintenanceMessage: string | null;
maintenanceEstimatedTime: string | null;
}
};

View File

@@ -343,7 +343,9 @@ async function updateHttpResource(
const isLicensed = await isLicensedOrSubscribed(resource.orgId);
if (build == "enterprise" && !isLicensed) {
logger.warn("Server is not licensed! Clearing set maintenance screen values");
logger.warn(
"Server is not licensed! Clearing set maintenance screen values"
);
// null the maintenance mode fields if not licensed
updateData.maintenanceModeEnabled = undefined;
updateData.maintenanceModeType = undefined;

View File

@@ -39,12 +39,12 @@ async function getLatestNewtVersion(): Promise<string | null> {
return null;
}
const tags = await response.json();
let tags = await response.json();
if (!Array.isArray(tags) || tags.length === 0) {
logger.warn("No tags found for Newt repository");
return null;
}
tags = tags.filter((version) => !version.name.includes("rc"));
const latestVersion = tags[0].name;
cache.set("latestNewtVersion", latestVersion);

View File

@@ -11,7 +11,11 @@ import {
userSiteResources
} from "@server/db";
import { getUniqueSiteResourceName } from "@server/db/names";
import { getNextAvailableAliasAddress, isIpInCidr, portRangeStringSchema } from "@server/lib/ip";
import {
getNextAvailableAliasAddress,
isIpInCidr,
portRangeStringSchema
} from "@server/lib/ip";
import { rebuildClientAssociationsFromSiteResource } from "@server/lib/rebuildClientAssociations";
import response from "@server/lib/response";
import logger from "@server/logger";
@@ -69,7 +73,10 @@ const createSiteResourceSchema = z
const domainRegex =
/^(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)*[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?$/;
const isValidDomain = domainRegex.test(data.destination);
const isValidAlias = data.alias !== undefined && data.alias !== null && data.alias.trim() !== "";
const isValidAlias =
data.alias !== undefined &&
data.alias !== null &&
data.alias.trim() !== "";
return isValidDomain && isValidAlias; // require the alias to be set in the case of domain
}
@@ -182,7 +189,9 @@ export async function createSiteResource(
.limit(1);
if (!org) {
return next(createHttpError(HttpCode.NOT_FOUND, "Organization not found"));
return next(
createHttpError(HttpCode.NOT_FOUND, "Organization not found")
);
}
if (!org.subnet || !org.utilitySubnet) {
@@ -195,10 +204,13 @@ export async function createSiteResource(
}
// Only check if destination is an IP address
const isIp = z.union([z.ipv4(), z.ipv6()]).safeParse(destination).success;
const isIp = z
.union([z.ipv4(), z.ipv6()])
.safeParse(destination).success;
if (
isIp &&
(isIpInCidr(destination, org.subnet) || isIpInCidr(destination, org.utilitySubnet))
(isIpInCidr(destination, org.subnet) ||
isIpInCidr(destination, org.utilitySubnet))
) {
return next(
createHttpError(

View File

@@ -88,9 +88,7 @@ export async function deleteSiteResource(
);
});
logger.info(
`Deleted site resource ${siteResourceId}`
);
logger.info(`Deleted site resource ${siteResourceId}`);
return response(res, {
data: { message: "Site resource deleted successfully" },

View File

@@ -204,7 +204,9 @@ export async function updateSiteResource(
.limit(1);
if (!org) {
return next(createHttpError(HttpCode.NOT_FOUND, "Organization not found"));
return next(
createHttpError(HttpCode.NOT_FOUND, "Organization not found")
);
}
if (!org.subnet || !org.utilitySubnet) {
@@ -217,10 +219,13 @@ export async function updateSiteResource(
}
// Only check if destination is an IP address
const isIp = z.union([z.ipv4(), z.ipv6()]).safeParse(destination).success;
const isIp = z
.union([z.ipv4(), z.ipv6()])
.safeParse(destination).success;
if (
isIp &&
(isIpInCidr(destination!, org.subnet) || isIpInCidr(destination!, org.utilitySubnet))
(isIpInCidr(destination!, org.subnet) ||
isIpInCidr(destination!, org.utilitySubnet))
) {
return next(
createHttpError(
@@ -295,7 +300,7 @@ export async function updateSiteResource(
const [insertedSiteResource] = await trx
.insert(siteResources)
.values({
...existingSiteResource,
...existingSiteResource
})
.returning();
@@ -517,9 +522,14 @@ export async function handleMessagingForUpdatedSiteResource(
site: { siteId: number; orgId: string },
trx: Transaction
) {
logger.debug("handleMessagingForUpdatedSiteResource: existingSiteResource is: ", existingSiteResource);
logger.debug("handleMessagingForUpdatedSiteResource: updatedSiteResource is: ", updatedSiteResource);
logger.debug(
"handleMessagingForUpdatedSiteResource: existingSiteResource is: ",
existingSiteResource
);
logger.debug(
"handleMessagingForUpdatedSiteResource: updatedSiteResource is: ",
updatedSiteResource
);
const { mergedAllClients } =
await rebuildClientAssociationsFromSiteResource(

View File

@@ -60,11 +60,11 @@ export default async function migration() {
);
await db.execute(
sql`ALTER TABLE "siteResources" ADD COLUMN "tcpPortRangeString" varchar;`
sql`ALTER TABLE "siteResources" ADD COLUMN "tcpPortRangeString" varchar NOT NULL DEFAULT '*';`
);
await db.execute(
sql`ALTER TABLE "siteResources" ADD COLUMN "udpPortRangeString" varchar;`
sql`ALTER TABLE "siteResources" ADD COLUMN "udpPortRangeString" varchar NOT NULL DEFAULT '*';`
);
await db.execute(

View File

@@ -73,16 +73,18 @@ export default async function migration() {
).run();
db.prepare(
`ALTER TABLE 'siteResources' ADD 'tcpPortRangeString' text;`
`ALTER TABLE 'siteResources' ADD 'tcpPortRangeString' text DEFAULT '*' NOT NULL;`
).run();
db.prepare(
`ALTER TABLE 'siteResources' ADD 'udpPortRangeString' text;`
`ALTER TABLE 'siteResources' ADD 'udpPortRangeString' text DEFAULT '*' NOT NULL;`
).run();
db.prepare(
`ALTER TABLE 'siteResources' ADD 'disableIcmp' integer;`
`ALTER TABLE 'siteResources' ADD 'disableIcmp' integer NOT NULL DEFAULT false;`
).run();
})();
db.pragma("foreign_keys = ON");

View File

@@ -62,6 +62,7 @@ export default function GeneralPage() {
const [variant, setVariant] = useState<"oidc" | "google" | "azure">("oidc");
const { isUnlocked } = useLicenseStatusContext();
const dashboardRedirectUrl = `${env.app.dashboardUrl}/auth/idp/${idpId}/oidc/callback`;
const [redirectUrl, setRedirectUrl] = useState(
`${env.app.dashboardUrl}/auth/idp/${idpId}/oidc/callback`
);
@@ -423,11 +424,18 @@ export default function GeneralPage() {
<InfoSections cols={3}>
<InfoSection>
<InfoSectionTitle>
{t("redirectUrl")}
{t("orgIdpRedirectUrls")}
</InfoSectionTitle>
<InfoSectionContent>
<CopyToClipboard text={redirectUrl} />
</InfoSectionContent>
{redirectUrl !== dashboardRedirectUrl && (
<InfoSectionContent>
<CopyToClipboard
text={dashboardRedirectUrl}
/>
</InfoSectionContent>
)}
</InfoSection>
</InfoSections>

View File

@@ -285,7 +285,7 @@ export default function Page() {
<Button
variant="outline"
onClick={() => {
router.push("/admin/idp");
router.push(`/${params.orgId}/settings/idp`);
}}
>
{t("idpSeeAll")}

View File

@@ -1,17 +1,10 @@
import { internal, priv } from "@app/lib/api";
import { internal } from "@app/lib/api";
import { authCookieHeader } from "@app/lib/api/cookies";
import { AxiosResponse } from "axios";
import SettingsSectionTitle from "@app/components/SettingsSectionTitle";
import IdpTable, { IdpRow } from "@app/components/private/OrgIdpTable";
import { getTranslations } from "next-intl/server";
import { Alert, AlertDescription } from "@app/components/ui/alert";
import { cache } from "react";
import {
GetOrgSubscriptionResponse,
GetOrgTierResponse
} from "@server/routers/billing/types";
import { TierId } from "@server/lib/billing/tiers";
import { build } from "@server/build";
import { PaidFeaturesAlert } from "@app/components/PaidFeaturesAlert";
type OrgIdpPageProps = {
params: Promise<{ orgId: string }>;
@@ -35,21 +28,6 @@ export default async function OrgIdpPage(props: OrgIdpPageProps) {
const t = await getTranslations();
let subscriptionStatus: GetOrgTierResponse | null = null;
try {
const getSubscription = cache(() =>
priv.get<AxiosResponse<GetOrgTierResponse>>(
`/org/${params.orgId}/billing/tier`
)
);
const subRes = await getSubscription();
subscriptionStatus = subRes.data.data;
} catch {}
const subscribed =
build === "enterprise"
? true
: subscriptionStatus?.tier === TierId.STANDARD;
return (
<>
<SettingsSectionTitle
@@ -57,13 +35,7 @@ export default async function OrgIdpPage(props: OrgIdpPageProps) {
description={t("idpManageDescription")}
/>
{build === "saas" && !subscribed ? (
<Alert variant="info" className="mb-6">
<AlertDescription>
{t("idpDisabled")} {t("subscriptionRequiredToUse")}
</AlertDescription>
</Alert>
) : null}
<PaidFeaturesAlert />
<IdpTable idps={idps} orgId={params.orgId} />
</>

View File

@@ -71,7 +71,9 @@ export default async function GeneralSettingsPage({
<div className="space-y-6">
<OrgInfoCard />
<HorizontalTabs items={navItems}>{children}</HorizontalTabs>
<HorizontalTabs items={navItems}>
{children}
</HorizontalTabs>
</div>
</OrgUserProvider>
</OrgProvider>

View File

@@ -71,7 +71,7 @@ export default async function ClientResourcesPage(
niceId: siteResource.niceId,
tcpPortRangeString: siteResource.tcpPortRangeString || null,
udpPortRangeString: siteResource.udpPortRangeString || null,
disableIcmp: siteResource.disableIcmp || false,
disableIcmp: siteResource.disableIcmp || false
};
}
);

View File

@@ -16,7 +16,6 @@ import { SwitchInput } from "@app/components/SwitchInput";
import { Tag, TagInput } from "@app/components/tags/tag-input";
import { Alert, AlertDescription, AlertTitle } from "@app/components/ui/alert";
import { Button } from "@app/components/ui/button";
import { CheckboxWithLabel } from "@app/components/ui/checkbox";
import {
Form,
FormControl,
@@ -184,9 +183,6 @@ export default function ResourceAuthenticationPage() {
const [ssoEnabled, setSsoEnabled] = useState(resource.sso);
const [autoLoginEnabled, setAutoLoginEnabled] = useState(
resource.skipToIdpId !== null && resource.skipToIdpId !== undefined
);
const [selectedIdpId, setSelectedIdpId] = useState<number | null>(
resource.skipToIdpId || null
);
@@ -243,19 +239,8 @@ export default function ResourceAuthenticationPage() {
text: w.email
}))
);
if (autoLoginEnabled && !selectedIdpId && orgIdps.length > 0) {
setSelectedIdpId(orgIdps[0].idpId);
}
hasInitializedRef.current = true;
}, [
pageLoading,
resourceRoles,
resourceUsers,
whitelist,
autoLoginEnabled,
selectedIdpId,
orgIdps
]);
}, [pageLoading, resourceRoles, resourceUsers, whitelist, orgIdps]);
const [, submitUserRolesForm, loadingSaveUsersRoles] = useActionState(
onSubmitUsersRoles,
@@ -269,16 +254,6 @@ export default function ResourceAuthenticationPage() {
const data = usersRolesForm.getValues();
try {
// Validate that an IDP is selected if auto login is enabled
if (autoLoginEnabled && !selectedIdpId) {
toast({
variant: "destructive",
title: t("error"),
description: t("selectIdpRequired")
});
return;
}
const jobs = [
api.post(`/resource/${resource.resourceId}/roles`, {
roleIds: data.roles.map((i) => parseInt(i.id))
@@ -288,7 +263,7 @@ export default function ResourceAuthenticationPage() {
}),
api.post(`/resource/${resource.resourceId}`, {
sso: ssoEnabled,
skipToIdpId: autoLoginEnabled ? selectedIdpId : null
skipToIdpId: selectedIdpId
})
];
@@ -296,7 +271,7 @@ export default function ResourceAuthenticationPage() {
updateResource({
sso: ssoEnabled,
skipToIdpId: autoLoginEnabled ? selectedIdpId : null
skipToIdpId: selectedIdpId
});
updateAuthInfo({
@@ -619,88 +594,53 @@ export default function ResourceAuthenticationPage() {
)}
{ssoEnabled && allIdps.length > 0 && (
<>
<div className="space-y-2 mb-3">
<CheckboxWithLabel
label={t(
"autoLoginExternalIdp"
)}
checked={autoLoginEnabled}
onCheckedChange={(
checked
) => {
setAutoLoginEnabled(
checked as boolean
<div className="space-y-2">
<label className="text-sm font-medium">
{t("defaultIdentityProvider")}
</label>
<Select
onValueChange={(value) => {
if (value === "none") {
setSelectedIdpId(null);
} else {
setSelectedIdpId(
parseInt(value)
);
if (
checked &&
allIdps.length > 0
) {
setSelectedIdpId(
allIdps[0].id
);
} else {
setSelectedIdpId(
null
);
}
}}
/>
<p className="text-sm text-muted-foreground">
{t(
"autoLoginExternalIdpDescription"
)}
</p>
</div>
{autoLoginEnabled && (
<div className="space-y-2">
<label className="text-sm font-medium">
{t(
"defaultIdentityProvider"
}
}}
value={
selectedIdpId
? selectedIdpId.toString()
: "none"
}
>
<SelectTrigger className="w-full mt-1">
<SelectValue
placeholder={t(
"selectIdpPlaceholder"
)}
</label>
<Select
onValueChange={(
value
) =>
setSelectedIdpId(
parseInt(value)
)
}
value={
selectedIdpId
? selectedIdpId.toString()
: undefined
}
>
<SelectTrigger className="w-full mt-1">
<SelectValue
placeholder={t(
"selectIdpPlaceholder"
)}
/>
</SelectTrigger>
<SelectContent>
{allIdps.map(
(idp) => (
<SelectItem
key={
idp.id
}
value={idp.id.toString()}
>
{
idp.text
}
</SelectItem>
)
)}
</SelectContent>
</Select>
</div>
)}
</>
/>
</SelectTrigger>
<SelectContent>
<SelectItem value="none">
{t("none")}
</SelectItem>
{allIdps.map((idp) => (
<SelectItem
key={idp.id}
value={idp.id.toString()}
>
{idp.text}
</SelectItem>
))}
</SelectContent>
</Select>
<p className="text-sm text-muted-foreground">
{t(
"defaultIdentityProviderDescription"
)}
</p>
</div>
)}
</form>
</Form>

View File

@@ -164,6 +164,10 @@ function MaintenanceSectionForm({
return isEnterpriseNotLicensed || isSaasNotSubscribed;
};
if (!resource.http) {
return null;
}
return (
<SettingsSection>
<SettingsSectionHeader>
@@ -189,7 +193,7 @@ function MaintenanceSectionForm({
name="maintenanceModeEnabled"
render={({ field }) => {
const isDisabled =
isSecurityFeatureDisabled();
isSecurityFeatureDisabled() || resource.http === false;
return (
<FormItem>
@@ -437,9 +441,16 @@ export default function GeneralForm() {
);
const resourceFullDomainName = useMemo(() => {
const url = new URL(resourceFullDomain);
return url.hostname;
}, [resourceFullDomain]);
if (!resource.fullDomain) {
return "";
}
try {
const url = new URL(resourceFullDomain);
return url.hostname;
} catch {
return "";
}
}, [resourceFullDomain, resource.fullDomain]);
const [selectedDomain, setSelectedDomain] = useState<{
domainId: string;

View File

@@ -338,7 +338,7 @@ function ProxyResourceTargetsForm({
<div
className={`flex items-center gap-2 ${status === "healthy" ? "text-green-500" : status === "unhealthy" ? "text-destructive" : ""}`}
>
<Settings className="h-3 w-3" />
<Settings className="h-4 w-4 text-foreground" />
{getStatusText(status)}
</div>
</Button>

View File

@@ -118,8 +118,7 @@ export default function ResourceRules(props: {
const [countrySelectValue, setCountrySelectValue] = useState("");
const [openAddRuleCountrySelect, setOpenAddRuleCountrySelect] =
useState(false);
const [openAddRuleAsnSelect, setOpenAddRuleAsnSelect] =
useState(false);
const [openAddRuleAsnSelect, setOpenAddRuleAsnSelect] = useState(false);
const router = useRouter();
const t = useTranslations();
const { env } = useEnvContext();
@@ -181,7 +180,7 @@ export default function ResourceRules(props: {
// Normalize ASN value
if (data.match === "ASN") {
const originalValue = data.value.toUpperCase();
// Handle special "ALL" case
if (originalValue === "ALL" || originalValue === "AS0") {
data.value = "ALL";
@@ -542,7 +541,11 @@ export default function ResourceRules(props: {
updateRule(row.original.ruleId, {
match: value,
value:
value === "COUNTRY" ? "US" : value === "ASN" ? "AS15169" : row.original.value
value === "COUNTRY"
? "US"
: value === "ASN"
? "AS15169"
: row.original.value
})
}
>
@@ -559,9 +562,7 @@ export default function ResourceRules(props: {
</SelectItem>
)}
{isMaxmindAsnAvailable && (
<SelectItem value="ASN">
{RuleMatch.ASN}
</SelectItem>
<SelectItem value="ASN">{RuleMatch.ASN}</SelectItem>
)}
</SelectContent>
</Select>
@@ -654,9 +655,7 @@ export default function ResourceRules(props: {
</PopoverTrigger>
<PopoverContent className="min-w-[200px] p-0">
<Command>
<CommandInput
placeholder="Search ASNs or enter custom..."
/>
<CommandInput placeholder="Search ASNs or enter custom..." />
<CommandList>
<CommandEmpty>
No ASN found. Enter a custom ASN below.
@@ -665,7 +664,9 @@ export default function ResourceRules(props: {
{MAJOR_ASNS.map((asn) => (
<CommandItem
key={asn.code}
value={asn.name + " " + asn.code}
value={
asn.name + " " + asn.code
}
onSelect={() => {
updateRule(
row.original.ruleId,
@@ -1056,8 +1057,8 @@ export default function ResourceRules(props: {
</PopoverContent>
</Popover>
) : addRuleForm.watch(
"match"
) === "ASN" ? (
"match"
) === "ASN" ? (
<Popover
open={
openAddRuleAsnSelect
@@ -1086,21 +1087,27 @@ export default function ResourceRules(props: {
field.value
)
?.name +
" (" +
field.value +
")" || field.value
" (" +
field.value +
")" ||
field.value
: "Select ASN"}
<ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
</Button>
</PopoverTrigger>
<PopoverContent className="w-full p-0">
<Command>
<CommandInput
placeholder="Search ASNs or enter custom..."
/>
<CommandInput placeholder="Search ASNs or enter custom..." />
<CommandList>
<CommandEmpty>
No ASN found. Use the custom input below.
No
ASN
found.
Use
the
custom
input
below.
</CommandEmpty>
<CommandGroup>
{MAJOR_ASNS.map(
@@ -1112,7 +1119,9 @@ export default function ResourceRules(props: {
asn.code
}
value={
asn.name + " " + asn.code
asn.name +
" " +
asn.code
}
onSelect={() => {
field.onChange(
@@ -1138,6 +1147,7 @@ export default function ResourceRules(props: {
{
asn.code
}
)
</CommandItem>
)
@@ -1148,14 +1158,32 @@ export default function ResourceRules(props: {
<div className="border-t p-2">
<Input
placeholder="Enter custom ASN (e.g., AS15169)"
onKeyDown={(e) => {
if (e.key === "Enter") {
const value = e.currentTarget.value
.toUpperCase()
.replace(/^AS/, "");
if (/^\d+$/.test(value)) {
field.onChange("AS" + value);
setOpenAddRuleAsnSelect(false);
onKeyDown={(
e
) => {
if (
e.key ===
"Enter"
) {
const value =
e.currentTarget.value
.toUpperCase()
.replace(
/^AS/,
""
);
if (
/^\d+$/.test(
value
)
) {
field.onChange(
"AS" +
value
);
setOpenAddRuleAsnSelect(
false
);
}
}
}}

View File

@@ -756,7 +756,9 @@ WantedBy=default.target`
render={({ field }) => (
<FormItem className="md:col-start-1 md:col-span-2">
<FormLabel>
{t("siteAddress")}
{t(
"siteAddress"
)}
</FormLabel>
<FormControl>
<Input
@@ -909,7 +911,7 @@ WantedBy=default.target`
? "squareOutlinePrimary"
: "squareOutline"
}
className={`flex-1 min-w-[120px] ${platform === os ? "bg-primary/10" : ""} shadow-none`}
className={`flex-1 min-w-30 ${platform === os ? "bg-primary/10" : ""} shadow-none`}
onClick={() => {
setPlatform(os);
}}
@@ -940,7 +942,7 @@ WantedBy=default.target`
? "squareOutlinePrimary"
: "squareOutline"
}
className={`flex-1 min-w-[120px] ${architecture === arch ? "bg-primary/10" : ""} shadow-none`}
className={`flex-1 min-w-30 ${architecture === arch ? "bg-primary/10" : ""} shadow-none`}
onClick={() =>
setArchitecture(
arch

View File

@@ -87,8 +87,6 @@ export default async function OrgAuthPage(props: {
redirect(env.app.dashboardUrl);
}
console.log(user, forceLogin);
if (user && !forceLogin) {
let redirectToken: string | undefined;
try {

View File

@@ -162,3 +162,32 @@ p {
#nprogress .bar {
background: var(--color-primary) !important;
}
@keyframes dot-pulse {
0%, 80%, 100% {
opacity: 0.3;
transform: scale(0.8);
}
40% {
opacity: 1;
transform: scale(1);
}
}
@layer utilities {
.animate-dot-pulse {
animation: dot-pulse 1.4s ease-in-out infinite;
}
/* Use JavaScript-set viewport height for mobile to handle keyboard properly */
.h-screen-safe {
height: 100vh; /* Default for desktop and fallback */
}
/* Only apply custom viewport height on mobile */
@media (max-width: 767px) {
.h-screen-safe {
height: var(--vh, 100vh); /* Use CSS variable set by ViewportHeightFix on mobile */
}
}
}

View File

@@ -22,6 +22,7 @@ import { TopLoader } from "@app/components/Toploader";
import Script from "next/script";
import { TanstackQueryProvider } from "@app/components/TanstackQueryProvider";
import { TailwindIndicator } from "@app/components/TailwindIndicator";
import { ViewportHeightFix } from "@app/components/ViewportHeightFix";
export const metadata: Metadata = {
title: `Dashboard - ${process.env.BRANDING_APP_NAME || "Pangolin"}`,
@@ -77,7 +78,7 @@ export default async function RootLayout({
return (
<html suppressHydrationWarning lang={locale}>
<body className={`${font.className} h-screen overflow-hidden`}>
<body className={`${font.className} h-screen-safe overflow-hidden`}>
<TopLoader />
{build === "saas" && (
<Script
@@ -86,6 +87,7 @@ export default async function RootLayout({
strategy="afterInteractive"
/>
)}
<ViewportHeightFix />
<NextIntlClientProvider>
<ThemeProvider
attribute="class"

View File

@@ -66,4 +66,3 @@ export const ClientDownloadBanner = () => {
};
export default ClientDownloadBanner;

View File

@@ -99,14 +99,12 @@ export default function ClientResourcesTable({
siteId: number
) => {
try {
await api
.delete(`/site-resource/${resourceId}`)
.then(() => {
startTransition(() => {
router.refresh();
setIsDeleteModalOpen(false);
});
await api.delete(`/site-resource/${resourceId}`).then(() => {
startTransition(() => {
router.refresh();
setIsDeleteModalOpen(false);
});
});
} catch (e) {
console.error(t("resourceErrorDelete"), e);
toast({

View File

@@ -87,7 +87,12 @@ const isValidPortRangeString = (val: string | undefined | null): boolean => {
return false;
}
if (startPort < 1 || startPort > 65535 || endPort < 1 || endPort > 65535) {
if (
startPort < 1 ||
startPort > 65535 ||
endPort < 1 ||
endPort > 65535
) {
return false;
}
@@ -131,7 +136,10 @@ const getPortModeFromString = (val: string | undefined | null): PortMode => {
};
// Helper to get the port string for API from mode and custom value
const getPortStringFromMode = (mode: PortMode, customValue: string): string | undefined => {
const getPortStringFromMode = (
mode: PortMode,
customValue: string
): string | undefined => {
if (mode === "all") return "*";
if (mode === "blocked") return "";
return customValue;
@@ -1097,8 +1105,7 @@ export default function CreateInternalResourceDialog({
size="sm"
tags={
form.getValues()
.roles ||
[]
.roles || []
}
setTags={(
newRoles
@@ -1154,8 +1161,7 @@ export default function CreateInternalResourceDialog({
)}
tags={
form.getValues()
.users ||
[]
.users || []
}
size="sm"
setTags={(
@@ -1245,9 +1251,7 @@ export default function CreateInternalResourceDialog({
restrictTagsToAutocompleteOptions={
true
}
sortTags={
true
}
sortTags={true}
/>
</FormControl>
<FormMessage />

View File

@@ -1,9 +1,10 @@
"use client";
import React, { useState, useEffect, type ReactNode } from "react";
import React, { useState, useEffect, type ReactNode, useEffectEvent } from "react";
import { Card, CardContent } from "@app/components/ui/card";
import { X } from "lucide-react";
import { useTranslations } from "next-intl";
import { useEnvContext } from "@app/hooks/useEnvContext";
type DismissableBannerProps = {
storageKey: string;
@@ -25,6 +26,12 @@ export const DismissableBanner = ({
const [isDismissed, setIsDismissed] = useState(true);
const t = useTranslations();
const { env } = useEnvContext();
if (env.flags.disableProductHelpBanners) {
return null;
}
useEffect(() => {
const dismissedData = localStorage.getItem(storageKey);
if (dismissedData) {
@@ -95,4 +102,3 @@ export const DismissableBanner = ({
};
export default DismissableBanner;

View File

@@ -501,13 +501,19 @@ export default function EditInternalResourceDialog({
// ]);
await queryClient.invalidateQueries(
resourceQueries.siteResourceRoles({ siteResourceId: resource.id })
resourceQueries.siteResourceRoles({
siteResourceId: resource.id
})
);
await queryClient.invalidateQueries(
resourceQueries.siteResourceUsers({ siteResourceId: resource.id })
resourceQueries.siteResourceUsers({
siteResourceId: resource.id
})
);
await queryClient.invalidateQueries(
resourceQueries.siteResourceClients({ siteResourceId: resource.id })
resourceQueries.siteResourceClients({
siteResourceId: resource.id
})
);
toast({

View File

@@ -330,7 +330,7 @@ export default function ExitNodesTable({
isRefreshing={isRefreshing}
columnVisibility={{
type: false,
address: false,
address: false
}}
enableColumnVisibility={true}
/>

View File

@@ -37,7 +37,7 @@ export async function Layout({
(sidebarStateCookie !== "expanded" && defaultSidebarCollapsed);
return (
<div className="flex h-screen overflow-hidden">
<div className="flex h-screen-safe overflow-hidden">
{/* Desktop Sidebar */}
{showSidebar && (
<LayoutSidebar

View File

@@ -48,7 +48,7 @@ export function LayoutMobileMenu({
const t = useTranslations();
return (
<div className="shrink-0 md:hidden">
<div className="shrink-0 md:hidden sticky top-0 z-50">
<div className="h-16 flex items-center px-2">
<div className="flex items-center gap-4">
{showSidebar && (
@@ -72,7 +72,7 @@ export function LayoutMobileMenu({
<SheetDescription className="sr-only">
{t("navbarDescription")}
</SheetDescription>
<div className="flex-1 overflow-y-auto">
<div className="flex-1 overflow-y-auto relative">
<div className="px-3">
<OrgSelector
orgId={orgId}
@@ -83,7 +83,7 @@ export function LayoutMobileMenu({
<div className="px-3">
{!isAdminPage &&
user.serverAdmin && (
<div className="pb-3">
<div className="py-2">
<Link
href="/admin"
className={cn(
@@ -113,6 +113,7 @@ export function LayoutMobileMenu({
}
/>
</div>
<div className="sticky bottom-0 left-0 right-0 h-8 pointer-events-none bg-gradient-to-t from-card to-transparent" />
</div>
<div className="px-3 pt-3 pb-3 space-y-4 border-t shrink-0">
<SupporterStatus />

View File

@@ -116,10 +116,12 @@ export function LayoutSidebar({
isCollapsed={isSidebarCollapsed}
/>
</div>
<div className={cn(
"w-full border-b border-border",
isSidebarCollapsed && "mb-2"
)} />
<div
className={cn(
"w-full border-b border-border",
isSidebarCollapsed && "mb-2"
)}
/>
<div className="flex-1 overflow-y-auto relative">
<div className="px-2 pt-1">
{!isAdminPage && user.serverAdmin && (

View File

@@ -120,7 +120,9 @@ export default function LoginForm({
const focusInput = () => {
// Try using the ref first
if (otpContainerRef.current) {
const hiddenInput = otpContainerRef.current.querySelector('input') as HTMLInputElement;
const hiddenInput = otpContainerRef.current.querySelector(
"input"
) as HTMLInputElement;
if (hiddenInput) {
hiddenInput.focus();
return;
@@ -128,17 +130,23 @@ export default function LoginForm({
}
// Fallback: query the DOM
const otpContainer = document.querySelector('[data-slot="input-otp"]');
const otpContainer = document.querySelector(
'[data-slot="input-otp"]'
);
if (!otpContainer) return;
const hiddenInput = otpContainer.querySelector('input') as HTMLInputElement;
const hiddenInput = otpContainer.querySelector(
"input"
) as HTMLInputElement;
if (hiddenInput) {
hiddenInput.focus();
return;
}
// Last resort: click the first slot
const firstSlot = otpContainer.querySelector('[data-slot="input-otp-slot"]') as HTMLElement;
const firstSlot = otpContainer.querySelector(
'[data-slot="input-otp-slot"]'
) as HTMLElement;
if (firstSlot) {
firstSlot.click();
}
@@ -508,7 +516,10 @@ export default function LoginForm({
render={({ field }) => (
<FormItem>
<FormControl>
<div ref={otpContainerRef} className="flex justify-center">
<div
ref={otpContainerRef}
className="flex justify-center"
>
<InputOTP
maxLength={6}
{...field}

View File

@@ -11,9 +11,7 @@ type MachineClientsBannerProps = {
orgId: string;
};
export const MachineClientsBanner = ({
orgId
}: MachineClientsBannerProps) => {
export const MachineClientsBanner = ({ orgId }: MachineClientsBannerProps) => {
const t = useTranslations();
return (
@@ -57,4 +55,3 @@ export const MachineClientsBanner = ({
};
export default MachineClientsBanner;

View File

@@ -39,4 +39,3 @@ export default function OrgInfoCard({}: OrgInfoCardProps) {
</Alert>
);
}

View File

@@ -119,4 +119,3 @@ export default async function OrgLoginPage({
</div>
);
}

View File

@@ -95,7 +95,7 @@ export function OrgSelectionForm() {
<p className="text-sm text-muted-foreground">
{t("orgAuthWhatsThis")}{" "}
<Link
href="https://docs.pangolin.net/manage/identity-providers/add-an-idp"
href="https://docs.pangolin.net/manage/organizations/org-id"
target="_blank"
rel="noopener noreferrer"
className="underline"

View File

@@ -51,4 +51,3 @@ export const PrivateResourcesBanner = ({
};
export default PrivateResourcesBanner;

View File

@@ -41,7 +41,10 @@ export default function ProductUpdates({
const data = useQueries({
queries: [
productUpdatesQueries.list(env.app.notifications.product_updates),
productUpdatesQueries.list(
env.app.notifications.product_updates,
env.app.version
),
productUpdatesQueries.latestVersion(
env.app.notifications.new_releases
)
@@ -189,7 +192,7 @@ function ProductUpdatesListPopup({
<div
className={cn(
"relative z-1 cursor-pointer block group",
"rounded-md border border-primary/30 bg-gradient-to-br dark:from-primary/20 from-primary/20 via-background to-background p-2 py-3 w-full flex flex-col gap-2 text-sm",
"rounded-md border border-primary/30 bg-linear-to-br dark:from-primary/20 from-primary/20 via-background to-background p-2 py-3 w-full flex flex-col gap-2 text-sm",
"transition duration-300 ease-in-out",
"data-closed:opacity-0 data-closed:translate-y-full"
)}
@@ -343,7 +346,7 @@ function NewVersionAvailable({
rel="noopener noreferrer"
className={cn(
"relative z-2 group cursor-pointer block",
"rounded-md border border-primary/30 bg-gradient-to-br dark:from-primary/20 from-primary/20 via-background to-background p-2 py-3 w-full flex flex-col gap-2 text-sm",
"rounded-md border border-primary/30 bg-linear-to-br dark:from-primary/20 from-primary/20 via-background to-background p-2 py-3 w-full flex flex-col gap-2 text-sm",
"transition duration-300 ease-in-out",
"data-closed:opacity-0 data-closed:translate-y-full"
)}

View File

@@ -20,4 +20,3 @@ export const ProxyResourcesBanner = () => {
};
export default ProxyResourcesBanner;

View File

@@ -198,7 +198,7 @@ export default function ProxyResourcesTable({
if (!targets || targets.length === 0) {
return (
<div className="flex items-center gap-2">
<div id="LOOK_FOR_ME" className="flex items-center gap-2">
<StatusIcon status="unknown" />
<span className="text-sm">
{t("resourcesTableNoTargets")}

View File

@@ -32,12 +32,6 @@ export default function ResourceInfoBox({}: ResourceInfoBoxType) {
<InfoSections
cols={resource.http && env.flags.usePangolinDns ? 5 : 4}
>
<InfoSection>
<InfoSectionTitle>URL</InfoSectionTitle>
<InfoSectionContent>
<CopyToClipboard text={fullUrl} isLink={true} />
</InfoSectionContent>
</InfoSection>
<InfoSection>
<InfoSectionTitle>{t("identifier")}</InfoSectionTitle>
<InfoSectionContent>
@@ -46,6 +40,12 @@ export default function ResourceInfoBox({}: ResourceInfoBoxType) {
</InfoSection>
{resource.http ? (
<>
<InfoSection>
<InfoSectionTitle>URL</InfoSectionTitle>
<InfoSectionContent>
<CopyToClipboard text={fullUrl} isLink={true} />
</InfoSectionContent>
</InfoSection>
<InfoSection>
<InfoSectionTitle>
{t("authentication")}

View File

@@ -1,6 +1,6 @@
"use client";
import {Button} from "@app/components/ui/button";
import { Button } from "@app/components/ui/button";
import {
Form,
FormControl,
@@ -9,12 +9,12 @@ import {
FormLabel,
FormMessage
} from "@app/components/ui/form";
import {Input} from "@app/components/ui/input";
import {toast} from "@app/hooks/useToast";
import {zodResolver} from "@hookform/resolvers/zod";
import {useEffect, useState} from "react";
import {useForm} from "react-hook-form";
import {z} from "zod";
import { Input } from "@app/components/ui/input";
import { toast } from "@app/hooks/useToast";
import { zodResolver } from "@hookform/resolvers/zod";
import { useEffect, useState } from "react";
import { useForm } from "react-hook-form";
import { z } from "zod";
import {
Credenza,
CredenzaBody,
@@ -25,14 +25,14 @@ import {
CredenzaHeader,
CredenzaTitle
} from "@app/components/Credenza";
import {formatAxiosError} from "@app/lib/api";
import {AxiosResponse} from "axios";
import {Resource} from "@server/db";
import {createApiClient} from "@app/lib/api";
import {useEnvContext} from "@app/hooks/useEnvContext";
import {useTranslations} from "next-intl";
import {SwitchInput} from "@/components/SwitchInput";
import {InfoPopup} from "@/components/ui/info-popup";
import { formatAxiosError } from "@app/lib/api";
import { AxiosResponse } from "axios";
import { Resource } from "@server/db";
import { createApiClient } from "@app/lib/api";
import { useEnvContext } from "@app/hooks/useEnvContext";
import { useTranslations } from "next-intl";
import { SwitchInput } from "@/components/SwitchInput";
import { InfoPopup } from "@/components/ui/info-popup";
const setHeaderAuthFormSchema = z.object({
user: z.string().min(4).max(100),
@@ -56,11 +56,11 @@ type SetHeaderAuthFormProps = {
};
export default function SetResourceHeaderAuthForm({
open,
setOpen,
resourceId,
onSetHeaderAuth
}: SetHeaderAuthFormProps) {
open,
setOpen,
resourceId,
onSetHeaderAuth
}: SetHeaderAuthFormProps) {
const api = createApiClient(useEnvContext());
const t = useTranslations();
@@ -82,11 +82,14 @@ export default function SetResourceHeaderAuthForm({
async function onSubmit(data: SetHeaderAuthFormValues) {
setLoading(true);
api.post<AxiosResponse<Resource>>(`/resource/${resourceId}/header-auth`, {
user: data.user,
password: data.password,
extendedCompatibility: data.extendedCompatibility
})
api.post<AxiosResponse<Resource>>(
`/resource/${resourceId}/header-auth`,
{
user: data.user,
password: data.password,
extendedCompatibility: data.extendedCompatibility
}
)
.then(() => {
toast({
title: t("resourceHeaderAuthSetup"),
@@ -100,10 +103,10 @@ export default function SetResourceHeaderAuthForm({
.catch((e) => {
toast({
variant: "destructive",
title: t('resourceErrorHeaderAuthSetup'),
title: t("resourceErrorHeaderAuthSetup"),
description: formatAxiosError(
e,
t('resourceErrorHeaderAuthSetupDescription')
t("resourceErrorHeaderAuthSetupDescription")
)
});
})
@@ -139,7 +142,7 @@ export default function SetResourceHeaderAuthForm({
<FormField
control={form.control}
name="user"
render={({field}) => (
render={({ field }) => (
<FormItem>
<FormLabel>{t("user")}</FormLabel>
<FormControl>
@@ -149,14 +152,14 @@ export default function SetResourceHeaderAuthForm({
{...field}
/>
</FormControl>
<FormMessage/>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="password"
render={({field}) => (
render={({ field }) => (
<FormItem>
<FormLabel>
{t("password")}
@@ -168,25 +171,31 @@ export default function SetResourceHeaderAuthForm({
{...field}
/>
</FormControl>
<FormMessage/>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="extendedCompatibility"
render={({field}) => (
render={({ field }) => (
<FormItem>
<FormControl>
<SwitchInput
id="header-auth-compatibility-toggle"
label={t("headerAuthCompatibility")}
info={t('headerAuthCompatibilityInfo')}
label={t(
"headerAuthCompatibility"
)}
info={t(
"headerAuthCompatibilityInfo"
)}
checked={field.value}
onCheckedChange={field.onChange}
onCheckedChange={
field.onChange
}
/>
</FormControl>
<FormMessage/>
<FormMessage />
</FormItem>
)}
/>

View File

@@ -91,10 +91,10 @@ export default function SetResourcePasswordForm({
.catch((e) => {
toast({
variant: "destructive",
title: t('resourceErrorPasswordSetup'),
title: t("resourceErrorPasswordSetup"),
description: formatAxiosError(
e,
t('resourceErrorPasswordSetupDescription')
t("resourceErrorPasswordSetupDescription")
)
});
})

View File

@@ -97,10 +97,10 @@ export default function SetResourcePincodeForm({
.catch((e) => {
toast({
variant: "destructive",
title: t('resourceErrorPincodeSetup'),
title: t("resourceErrorPincodeSetup"),
description: formatAxiosError(
e,
t('resourceErrorPincodeSetupDescription')
t("resourceErrorPincodeSetupDescription")
)
});
})

View File

@@ -37,4 +37,3 @@ export const SitesBanner = () => {
};
export default SitesBanner;

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