Compare commits

..

259 Commits

Author SHA1 Message Date
Owen Schwartz c207372f75 New translations en-us.json (Norwegian Bokmal) 2026-01-19 22:30:26 -08:00
Owen Schwartz 6606ec0abb New translations en-us.json (Chinese Simplified) 2026-01-19 22:30:24 -08:00
Owen Schwartz d17c821b77 New translations en-us.json (Turkish) 2026-01-19 22:30:23 -08:00
Owen Schwartz 08681fceff New translations en-us.json (Russian) 2026-01-19 22:30:21 -08:00
Owen Schwartz cc8dd44d92 New translations en-us.json (Portuguese) 2026-01-19 22:30:19 -08:00
Owen Schwartz 761666b0f1 New translations en-us.json (Polish) 2026-01-19 22:30:18 -08:00
Owen Schwartz 71b483e95e New translations en-us.json (Dutch) 2026-01-19 22:30:16 -08:00
Owen Schwartz 4f5ceb054a New translations en-us.json (Korean) 2026-01-19 22:30:15 -08:00
Owen Schwartz 560b5b48dc New translations en-us.json (Italian) 2026-01-19 22:30:13 -08:00
Owen Schwartz eb5d7f1c97 New translations en-us.json (German) 2026-01-19 22:30:12 -08:00
Owen Schwartz ae0245b35f New translations en-us.json (Czech) 2026-01-19 22:30:10 -08:00
Owen Schwartz 0fdbea622c New translations en-us.json (Bulgarian) 2026-01-19 22:30:08 -08:00
Owen Schwartz a69a29ef70 New translations en-us.json (Spanish) 2026-01-19 22:30:07 -08:00
Owen Schwartz d7089403f6 New translations en-us.json (French) 2026-01-19 22:30:05 -08:00
Owen Schwartz 37f943edd2 New translations en-us.json (Norwegian Bokmal) 2026-01-18 12:15:10 -08:00
Owen Schwartz a437b8937e New translations en-us.json (Chinese Simplified) 2026-01-18 12:15:08 -08:00
Owen Schwartz fa9de6db42 New translations en-us.json (Turkish) 2026-01-18 12:15:07 -08:00
Owen Schwartz 533bd1b68a New translations en-us.json (Russian) 2026-01-18 12:15:06 -08:00
Owen Schwartz eba84db036 New translations en-us.json (Portuguese) 2026-01-18 12:15:04 -08:00
Owen Schwartz 07d848a40e New translations en-us.json (Polish) 2026-01-18 12:15:03 -08:00
Owen Schwartz 9f54e2efff New translations en-us.json (Dutch) 2026-01-18 12:15:01 -08:00
Owen Schwartz 77f1172640 New translations en-us.json (Korean) 2026-01-18 12:15:00 -08:00
Owen Schwartz f35946860a New translations en-us.json (Italian) 2026-01-18 12:14:59 -08:00
Owen Schwartz 5d28c3e50c New translations en-us.json (German) 2026-01-18 12:14:57 -08:00
Owen Schwartz 6d37c419c2 New translations en-us.json (Czech) 2026-01-18 12:14:56 -08:00
Owen Schwartz bcc0658a20 New translations en-us.json (Bulgarian) 2026-01-18 12:14:55 -08:00
Owen Schwartz 24e49d85e9 New translations en-us.json (Spanish) 2026-01-18 12:14:53 -08:00
Owen Schwartz cc06503fbe New translations en-us.json (French) 2026-01-18 12:14:52 -08:00
Owen Schwartz 984799b854 New translations en-us.json (Norwegian Bokmal) 2026-01-17 21:13:21 -08:00
Owen Schwartz 141d3db56c New translations en-us.json (Chinese Simplified) 2026-01-17 21:13:20 -08:00
Owen Schwartz 1d46a151ff New translations en-us.json (Turkish) 2026-01-17 21:13:18 -08:00
Owen Schwartz b03cb24d1d New translations en-us.json (Russian) 2026-01-17 21:13:17 -08:00
Owen Schwartz ffbd3931be New translations en-us.json (Portuguese) 2026-01-17 21:13:16 -08:00
Owen Schwartz 47f812616a New translations en-us.json (Polish) 2026-01-17 21:13:14 -08:00
Owen Schwartz b1d7d56a7a New translations en-us.json (Dutch) 2026-01-17 21:13:13 -08:00
Owen Schwartz 4bceb70d87 New translations en-us.json (Korean) 2026-01-17 21:13:12 -08:00
Owen Schwartz d23fbf2810 New translations en-us.json (Italian) 2026-01-17 21:13:11 -08:00
Owen Schwartz efb373503c New translations en-us.json (German) 2026-01-17 21:13:09 -08:00
Owen Schwartz 98d8c91f82 New translations en-us.json (Czech) 2026-01-17 21:13:08 -08:00
Owen Schwartz 04697abc41 New translations en-us.json (Bulgarian) 2026-01-17 21:13:07 -08:00
Owen Schwartz 6f63292b26 New translations en-us.json (Spanish) 2026-01-17 21:13:05 -08:00
Owen Schwartz c416dd275f New translations en-us.json (French) 2026-01-17 21:13:04 -08:00
Owen Schwartz 603b66780d New translations en-us.json (Norwegian Bokmal) 2026-01-16 14:50:10 -08:00
Owen Schwartz 6bb72a7447 New translations en-us.json (Chinese Simplified) 2026-01-16 14:50:08 -08:00
Owen Schwartz a679588d3b New translations en-us.json (Turkish) 2026-01-16 14:50:07 -08:00
Owen Schwartz 56114cb11a New translations en-us.json (Russian) 2026-01-16 14:50:06 -08:00
Owen Schwartz ccc6a64332 New translations en-us.json (Portuguese) 2026-01-16 14:50:04 -08:00
Owen Schwartz aee8d0131e New translations en-us.json (Polish) 2026-01-16 14:50:03 -08:00
Owen Schwartz 20fc347182 New translations en-us.json (Dutch) 2026-01-16 14:50:02 -08:00
Owen Schwartz 6462b3c482 New translations en-us.json (Korean) 2026-01-16 14:50:00 -08:00
Owen Schwartz c3d620a9b2 New translations en-us.json (Italian) 2026-01-16 14:49:59 -08:00
Owen Schwartz 1cfab08262 New translations en-us.json (German) 2026-01-16 14:49:58 -08:00
Owen Schwartz e70919b9e4 New translations en-us.json (Czech) 2026-01-16 14:49:57 -08:00
Owen Schwartz 157b673b64 New translations en-us.json (Bulgarian) 2026-01-16 14:49:55 -08:00
Owen Schwartz 2d71293a7c New translations en-us.json (Spanish) 2026-01-16 14:49:54 -08:00
Owen Schwartz ac30d28d7e New translations en-us.json (French) 2026-01-16 14:49:52 -08:00
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 3329 additions and 1179 deletions
+8 -3
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 }}
+1 -1
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
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`);
}
+143 -13
View File
@@ -56,6 +56,9 @@
"sitesBannerTitle": "Свържете се с мрежа.",
"sitesBannerDescription": "Сайтът е връзка с отдалечена мрежа, която позволява на Pangolin да предоставя достъп до ресурси, било то публични или частни, на потребители навсякъде. Инсталирайте мрежовия конектор на сайта (Newt) навсякъде, където можете да стартирате бинарен или контейнер, за да създадете връзката.",
"sitesBannerButtonText": "Инсталиране на сайт.",
"approvalsBannerTitle": "Approve or Deny Device Access",
"approvalsBannerDescription": "Review and approve or deny device access requests from users. When device approvals are required, users must get admin approval before their devices can connect to your organization's resources.",
"approvalsBannerButtonText": "Learn More",
"siteCreate": "Създайте сайт",
"siteCreateDescription2": "Следвайте стъпките по-долу, за да създадете и свържете нов сайт",
"siteCreateDescription": "Създайте нов сайт, за да започнете да свързвате ресурси",
@@ -257,6 +260,8 @@
"accessRolesSearch": "Търсене на роли...",
"accessRolesAdd": "Добавете роля",
"accessRoleDelete": "Изтриване на роля",
"accessApprovalsManage": "Manage Approvals",
"accessApprovalsDescription": "View and manage pending approvals for access to this organization",
"description": "Описание",
"inviteTitle": "Отворени покани",
"inviteDescription": "Управлявайте покани за други потребители да се присъединят към организацията",
@@ -450,6 +455,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 +746,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 +879,7 @@
"orgPolicyConfig": "Конфигуриране на достъп за организация",
"idpUpdatedDescription": "Идентификационният доставчик беше актуализиран успешно",
"redirectUrl": "URL за пренасочване",
"orgIdpRedirectUrls": "URL адреси за пренасочване",
"redirectUrlAbout": "За URL за пренасочване",
"redirectUrlAboutDescription": "Това е URL адресът, към който потребителите ще бъдат пренасочени след удостоверяване. Трябва да конфигурирате този URL адрес в настройките на доставчика на идентичност.",
"pangolinAuth": "Authent - Pangolin",
@@ -873,7 +903,7 @@
"inviteAlready": "Изглежда, че сте били поканени!",
"inviteAlreadyDescription": "За да приемете поканата, трябва да влезете или да създадете акаунт.",
"signupQuestion": "Вече имате акаунт?",
"login": "Влизане",
"login": "Log In",
"resourceNotFound": "Ресурсът не е намерен",
"resourceNotFoundDescription": "Ресурсът, който се опитвате да достъпите, не съществува.",
"pincodeRequirementsLength": "ПИН трябва да бъде точно 6 цифри",
@@ -953,13 +983,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 +1147,10 @@
"actionUpdateIdpOrg": "Актуализиране на IdP организация",
"actionCreateClient": "Създаване на клиент",
"actionDeleteClient": "Изтриване на клиент",
"actionArchiveClient": "Archive Client",
"actionUnarchiveClient": "Unarchive Client",
"actionBlockClient": "Block Client",
"actionUnblockClient": "Unblock Client",
"actionUpdateClient": "Актуализиране на клиент",
"actionListClients": "Списък с клиенти",
"actionGetClient": "Получаване на клиент",
@@ -1133,14 +1167,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 +1222,7 @@
"sidebarOverview": "Общ преглед",
"sidebarHome": "Начало",
"sidebarSites": "Сайтове",
"sidebarApprovals": "Approval Requests",
"sidebarResources": "Ресурси",
"sidebarProxyResources": "Публично",
"sidebarClientResources": "Частно",
@@ -1204,7 +1239,7 @@
"sidebarIdentityProviders": "Идентификационни доставчици",
"sidebarLicense": "Лиценз",
"sidebarClients": "Клиенти",
"sidebarUserDevices": "Потребители",
"sidebarUserDevices": "User Devices",
"sidebarMachineClients": "Машини",
"sidebarDomains": "Домейни",
"sidebarGeneral": "Управление.",
@@ -1276,6 +1311,7 @@
"setupErrorCreateAdmin": "Възникна грешка при създаване на админ акаунт.",
"certificateStatus": "Статус на сертификата",
"loading": "Зареждане",
"loadingAnalytics": "Loading Analytics",
"restart": "Рестарт",
"domains": "Домейни",
"domainsDescription": "Създайте и управлявайте наличните домейни в организацията",
@@ -1303,6 +1339,7 @@
"refreshError": "Неуспешно обновяване на данни",
"verified": "Потвърдено",
"pending": "Чакащо",
"pendingApproval": "Pending Approval",
"sidebarBilling": "Фактуриране",
"billing": "Фактуриране",
"orgBillingDescription": "Управлявайте информацията за плащане и абонаментите",
@@ -1419,7 +1456,7 @@
"securityKeyRemoveSuccess": "Ключът за защита е премахнат успешно",
"securityKeyRemoveError": "Неуспешно премахване на ключ за защита",
"securityKeyLoadError": "Неуспешно зареждане на ключове за защита",
"securityKeyLogin": "Продължете с ключа за сигурност",
"securityKeyLogin": "Use Security Key",
"securityKeyAuthError": "Неуспешно удостоверяване с ключ за сигурност",
"securityKeyRecommendation": "Регистрирайте резервен ключ за безопасност на друго устройство, за да сте сигурни, че винаги ще имате достъп до профила си",
"registering": "Регистрация...",
@@ -1479,7 +1516,7 @@
"IAgreeToThe": "Съгласен съм с",
"termsOfService": "условията за ползване",
"and": "и",
"privacyPolicy": "политиката за поверителност"
"privacyPolicy": "политика за поверителност."
},
"signUpMarketing": {
"keepMeInTheLoop": "Дръж ме в течение с новини, актуализации и нови функции чрез имейл."
@@ -1546,6 +1583,8 @@
"IntervalSeconds": "Интервал за здраве",
"timeoutSeconds": "Време за изчакване (сек)",
"timeIsInSeconds": "Времето е в секунди",
"requireDeviceApproval": "Require Device Approvals",
"requireDeviceApprovalDescription": "Users with this role need new devices approved by an admin before they can connect and access resources.",
"retryAttempts": "Опити за повторно",
"expectedResponseCodes": "Очаквани кодове за отговор",
"expectedResponseCodesDescription": "HTTP статус код, указващ здравословно състояние. Ако бъде оставено празно, между 200-300 се счита за здравословно.",
@@ -1875,7 +1914,7 @@
"orgAuthChooseIdpDescription": "Изберете своя доставчик на идентичност, за да продължите",
"orgAuthNoIdpConfigured": "Тази организация няма конфигурирани доставчици на идентичност. Можете да влезете с вашата Pangolin идентичност.",
"orgAuthSignInWithPangolin": "Впишете се с Pangolin",
"orgAuthSignInToOrg": "Влезте в организация.",
"orgAuthSignInToOrg": "Sign in to an organization",
"orgAuthSelectOrgTitle": "Вход в организация.",
"orgAuthSelectOrgDescription": "Въведете идентификатора на вашата организация, за да продължите.",
"orgAuthOrgIdPlaceholder": "вашата-организация",
@@ -2231,6 +2270,8 @@
"deviceCodeInvalidFormat": "Кодът трябва да бъде 9 символа (напр. A1AJ-N5JD)",
"deviceCodeInvalidOrExpired": "Невалиден или изтекъл код",
"deviceCodeVerifyFailed": "Неуспешна проверка на кода на устройството",
"deviceCodeValidating": "Validating device code...",
"deviceCodeVerifying": "Verifying device authorization...",
"signedInAs": "Вписан като",
"deviceCodeEnterPrompt": "Въведете кода, показан на устройството",
"continue": "Продължете",
@@ -2243,7 +2284,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 +2346,7 @@
"identifier": "Идентификатор",
"deviceLoginUseDifferentAccount": "Не сте вие? Използвайте друг акаунт.",
"deviceLoginDeviceRequestingAccessToAccount": "Устройство запитващо достъп до този акаунт.",
"loginSelectAuthenticationMethod": "Select an authentication method to continue.",
"noData": "Няма Данни",
"machineClients": "Машинни клиенти",
"install": "Инсталирай",
@@ -2349,6 +2391,7 @@
"enterConfirmation": "Въведете потвърждение.",
"blueprintViewDetails": "Подробности.",
"defaultIdentityProvider": "По подразбиране доставчик на идентичност.",
"defaultIdentityProviderDescription": "Когато е избран основен доставчик на идентичност, потребителят ще бъде автоматично пренасочен към доставчика за удостоверяване.",
"editInternalResourceDialogNetworkSettings": "Мрежови настройки.",
"editInternalResourceDialogAccessPolicy": "Политика за достъп.",
"editInternalResourceDialogAddRoles": "Добавяне на роли.",
@@ -2392,5 +2435,92 @@
"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",
"deviceInformation": "Device Information",
"deviceInformationDescription": "Information about the device and agent",
"platform": "Platform",
"macosVersion": "macOS Version",
"windowsVersion": "Windows Version",
"iosVersion": "iOS Version",
"androidVersion": "Android Version",
"osVersion": "OS Version",
"kernelVersion": "Kernel Version",
"deviceModel": "Device Model",
"serialNumber": "Serial Number",
"hostname": "Hostname",
"firstSeen": "First Seen",
"lastSeen": "Last Seen",
"deviceSettingsDescription": "View device information and settings",
"devicePendingApprovalDescription": "This device is waiting for approval",
"deviceBlockedDescription": "This device is currently blocked. It won't be able to connect to any resources unless unblocked.",
"unblockClient": "Unblock Client",
"unblockClientDescription": "The device has been unblocked",
"unarchiveClient": "Unarchive Client",
"unarchiveClientDescription": "The device has been unarchived",
"block": "Block",
"unblock": "Unblock",
"deviceActions": "Device Actions",
"deviceActionsDescription": "Manage device status and access",
"devicePendingApprovalBannerDescription": "This device is pending approval. It won't be able to connect to resources until approved.",
"connected": "Connected",
"disconnected": "Disconnected",
"approvalsEmptyStateTitle": "Device Approvals Not Enabled",
"approvalsEmptyStateDescription": "Enable device approvals for roles to require admin approval before users can connect new devices.",
"approvalsEmptyStateStep1Title": "Go to Roles",
"approvalsEmptyStateStep1Description": "Navigate to your organization's roles settings to configure device approvals.",
"approvalsEmptyStateStep2Title": "Enable Device Approvals",
"approvalsEmptyStateStep2Description": "Edit a role and enable the 'Require Device Approvals' option. Users with this role will need admin approval for new devices.",
"approvalsEmptyStatePreviewDescription": "Preview: When enabled, pending device requests will appear here for review",
"approvalsEmptyStateButtonText": "Manage Roles"
}
+143 -13
View File
@@ -56,6 +56,9 @@
"sitesBannerTitle": "Připojit jakoukoli síť",
"sitesBannerDescription": "Lokalita je připojení k vzdálené síti, která umožňuje Pangolinu poskytovat přístup k prostředkům, ať už veřejným nebo soukromým, uživatelům kdekoli. Nainstalujte síťový konektor (Newt) kamkoli, kam můžete spustit binární soubor nebo kontejner, aby bylo možné připojení navázat.",
"sitesBannerButtonText": "Nainstalovat lokalitu",
"approvalsBannerTitle": "Approve or Deny Device Access",
"approvalsBannerDescription": "Review and approve or deny device access requests from users. When device approvals are required, users must get admin approval before their devices can connect to your organization's resources.",
"approvalsBannerButtonText": "Learn More",
"siteCreate": "Vytvořit lokalitu",
"siteCreateDescription2": "Postupujte podle níže uvedených kroků, abyste vytvořili a připojili novou lokalitu",
"siteCreateDescription": "Vytvořit nový web pro zahájení připojování zdrojů",
@@ -257,6 +260,8 @@
"accessRolesSearch": "Hledat role...",
"accessRolesAdd": "Přidat roli",
"accessRoleDelete": "Odstranit roli",
"accessApprovalsManage": "Manage Approvals",
"accessApprovalsDescription": "View and manage pending approvals for access to this 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 +455,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 +746,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 +879,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 +903,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 +983,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 +1147,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 +1167,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 +1222,7 @@
"sidebarOverview": "Přehled",
"sidebarHome": "Domů",
"sidebarSites": "Stránky",
"sidebarApprovals": "Approval Requests",
"sidebarResources": "Zdroje",
"sidebarProxyResources": "Veřejnost",
"sidebarClientResources": "Soukromé",
@@ -1204,7 +1239,7 @@
"sidebarIdentityProviders": "Poskytovatelé identity",
"sidebarLicense": "Licence",
"sidebarClients": "Klienti",
"sidebarUserDevices": "Uživatelé",
"sidebarUserDevices": "User Devices",
"sidebarMachineClients": "Stroje a přístroje",
"sidebarDomains": "Domény",
"sidebarGeneral": "Spravovat",
@@ -1276,6 +1311,7 @@
"setupErrorCreateAdmin": "Došlo k chybě při vytváření účtu správce serveru.",
"certificateStatus": "Stav certifikátu",
"loading": "Načítání",
"loadingAnalytics": "Loading Analytics",
"restart": "Restartovat",
"domains": "Domény",
"domainsDescription": "Vytvořit a spravovat domény dostupné v organizaci",
@@ -1303,6 +1339,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 +1456,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 +1516,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 +1583,8 @@
"IntervalSeconds": "Interval zdraví",
"timeoutSeconds": "Časový limit (sek)",
"timeIsInSeconds": "Čas je v sekundách",
"requireDeviceApproval": "Require Device Approvals",
"requireDeviceApprovalDescription": "Users with this role need new devices approved by an admin before they can connect and 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 +1914,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 +2270,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 +2284,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 +2346,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 +2391,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 +2435,92 @@
"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",
"deviceInformation": "Device Information",
"deviceInformationDescription": "Information about the device and agent",
"platform": "Platform",
"macosVersion": "macOS Version",
"windowsVersion": "Windows Version",
"iosVersion": "iOS Version",
"androidVersion": "Android Version",
"osVersion": "OS Version",
"kernelVersion": "Kernel Version",
"deviceModel": "Device Model",
"serialNumber": "Serial Number",
"hostname": "Hostname",
"firstSeen": "First Seen",
"lastSeen": "Last Seen",
"deviceSettingsDescription": "View device information and settings",
"devicePendingApprovalDescription": "This device is waiting for approval",
"deviceBlockedDescription": "This device is currently blocked. It won't be able to connect to any resources unless unblocked.",
"unblockClient": "Unblock Client",
"unblockClientDescription": "The device has been unblocked",
"unarchiveClient": "Unarchive Client",
"unarchiveClientDescription": "The device has been unarchived",
"block": "Block",
"unblock": "Unblock",
"deviceActions": "Device Actions",
"deviceActionsDescription": "Manage device status and access",
"devicePendingApprovalBannerDescription": "This device is pending approval. It won't be able to connect to resources until approved.",
"connected": "Connected",
"disconnected": "Disconnected",
"approvalsEmptyStateTitle": "Device Approvals Not Enabled",
"approvalsEmptyStateDescription": "Enable device approvals for roles to require admin approval before users can connect new devices.",
"approvalsEmptyStateStep1Title": "Go to Roles",
"approvalsEmptyStateStep1Description": "Navigate to your organization's roles settings to configure device approvals.",
"approvalsEmptyStateStep2Title": "Enable Device Approvals",
"approvalsEmptyStateStep2Description": "Edit a role and enable the 'Require Device Approvals' option. Users with this role will need admin approval for new devices.",
"approvalsEmptyStatePreviewDescription": "Preview: When enabled, pending device requests will appear here for review",
"approvalsEmptyStateButtonText": "Manage Roles"
}
+143 -13
View File
@@ -56,6 +56,9 @@
"sitesBannerTitle": "Verbinde ein beliebiges Netzwerk",
"sitesBannerDescription": "Ein Standort ist eine Verbindung zu einem Remote-Netzwerk, die es Pangolin ermöglicht, Zugriff auf öffentliche oder private Ressourcen für Benutzer überall zu gewähren. Installieren Sie den Site Netzwerk Connector (Newt) wo auch immer Sie eine Binärdatei oder einen Container starten können, um die Verbindung herzustellen.",
"sitesBannerButtonText": "Standort installieren",
"approvalsBannerTitle": "Approve or Deny Device Access",
"approvalsBannerDescription": "Review and approve or deny device access requests from users. When device approvals are required, users must get admin approval before their devices can connect to your organization's resources.",
"approvalsBannerButtonText": "Learn More",
"siteCreate": "Standort erstellen",
"siteCreateDescription2": "Folge den nachfolgenden Schritten, um einen neuen Standort zu erstellen und zu verbinden",
"siteCreateDescription": "Erstellen Sie einen neuen Standort, um Ressourcen zu verbinden",
@@ -257,6 +260,8 @@
"accessRolesSearch": "Rollen suchen...",
"accessRolesAdd": "Rolle hinzufügen",
"accessRoleDelete": "Rolle löschen",
"accessApprovalsManage": "Manage Approvals",
"accessApprovalsDescription": "View and manage pending approvals for access to this organization",
"description": "Beschreibung",
"inviteTitle": "Einladungen öffnen",
"inviteDescription": "Einladungen für andere Benutzer verwalten, der Organisation beizutreten",
@@ -450,6 +455,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 +746,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 +879,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 +903,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 +983,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 +1147,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 +1167,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 +1222,7 @@
"sidebarOverview": "Übersicht",
"sidebarHome": "Zuhause",
"sidebarSites": "Standorte",
"sidebarApprovals": "Approval Requests",
"sidebarResources": "Ressourcen",
"sidebarProxyResources": "Öffentlich",
"sidebarClientResources": "Privat",
@@ -1204,7 +1239,7 @@
"sidebarIdentityProviders": "Identitätsanbieter",
"sidebarLicense": "Lizenz",
"sidebarClients": "Clients",
"sidebarUserDevices": "Benutzergeräte",
"sidebarUserDevices": "User Devices",
"sidebarMachineClients": "Maschinen",
"sidebarDomains": "Domänen",
"sidebarGeneral": "Verwalten",
@@ -1276,6 +1311,7 @@
"setupErrorCreateAdmin": "Beim Erstellen des Server-Admin-Kontos ist ein Fehler aufgetreten.",
"certificateStatus": "Zertifikatsstatus",
"loading": "Laden",
"loadingAnalytics": "Loading Analytics",
"restart": "Neustart",
"domains": "Domänen",
"domainsDescription": "Erstellen und verwalten der in der Organisation verfügbaren Domänen",
@@ -1303,6 +1339,7 @@
"refreshError": "Datenaktualisierung fehlgeschlagen",
"verified": "Verifiziert",
"pending": "Ausstehend",
"pendingApproval": "Pending Approval",
"sidebarBilling": "Abrechnung",
"billing": "Abrechnung",
"orgBillingDescription": "Zahlungsinformationen und Abonnements verwalten",
@@ -1419,7 +1456,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 +1516,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 +1583,8 @@
"IntervalSeconds": "Gesunder Intervall",
"timeoutSeconds": "Timeout (Sek.)",
"timeIsInSeconds": "Zeit ist in Sekunden",
"requireDeviceApproval": "Require Device Approvals",
"requireDeviceApprovalDescription": "Users with this role need new devices approved by an admin before they can connect and 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 +1914,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 +2270,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 +2284,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 +2346,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 +2391,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 +2435,92 @@
"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",
"deviceInformation": "Device Information",
"deviceInformationDescription": "Information about the device and agent",
"platform": "Platform",
"macosVersion": "macOS Version",
"windowsVersion": "Windows Version",
"iosVersion": "iOS Version",
"androidVersion": "Android Version",
"osVersion": "OS Version",
"kernelVersion": "Kernel Version",
"deviceModel": "Device Model",
"serialNumber": "Serial Number",
"hostname": "Hostname",
"firstSeen": "First Seen",
"lastSeen": "Last Seen",
"deviceSettingsDescription": "View device information and settings",
"devicePendingApprovalDescription": "This device is waiting for approval",
"deviceBlockedDescription": "This device is currently blocked. It won't be able to connect to any resources unless unblocked.",
"unblockClient": "Unblock Client",
"unblockClientDescription": "The device has been unblocked",
"unarchiveClient": "Unarchive Client",
"unarchiveClientDescription": "The device has been unarchived",
"block": "Block",
"unblock": "Unblock",
"deviceActions": "Device Actions",
"deviceActionsDescription": "Manage device status and access",
"devicePendingApprovalBannerDescription": "This device is pending approval. It won't be able to connect to resources until approved.",
"connected": "Connected",
"disconnected": "Disconnected",
"approvalsEmptyStateTitle": "Device Approvals Not Enabled",
"approvalsEmptyStateDescription": "Enable device approvals for roles to require admin approval before users can connect new devices.",
"approvalsEmptyStateStep1Title": "Go to Roles",
"approvalsEmptyStateStep1Description": "Navigate to your organization's roles settings to configure device approvals.",
"approvalsEmptyStateStep2Title": "Enable Device Approvals",
"approvalsEmptyStateStep2Description": "Edit a role and enable the 'Require Device Approvals' option. Users with this role will need admin approval for new devices.",
"approvalsEmptyStatePreviewDescription": "Preview: When enabled, pending device requests will appear here for review",
"approvalsEmptyStateButtonText": "Manage Roles"
}
+3 -1
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",
+143 -13
View File
@@ -56,6 +56,9 @@
"sitesBannerTitle": "Conectar cualquier red",
"sitesBannerDescription": "Un sitio es una conexión a una red remota que permite a Pangolin proporcionar acceso a recursos, públicos o privados, a usuarios en cualquier lugar. Instale el conector de red del sitio (Newt) en cualquier lugar donde pueda ejecutar un binario o contenedor para establecer la conexión.",
"sitesBannerButtonText": "Instalar sitio",
"approvalsBannerTitle": "Approve or Deny Device Access",
"approvalsBannerDescription": "Review and approve or deny device access requests from users. When device approvals are required, users must get admin approval before their devices can connect to your organization's resources.",
"approvalsBannerButtonText": "Learn More",
"siteCreate": "Crear sitio",
"siteCreateDescription2": "Siga los pasos siguientes para crear y conectar un nuevo sitio",
"siteCreateDescription": "Crear un nuevo sitio para empezar a conectar recursos",
@@ -257,6 +260,8 @@
"accessRolesSearch": "Buscar roles...",
"accessRolesAdd": "Añadir rol",
"accessRoleDelete": "Eliminar rol",
"accessApprovalsManage": "Manage Approvals",
"accessApprovalsDescription": "View and manage pending approvals for access to this organization",
"description": "Descripción",
"inviteTitle": "Invitaciones abiertas",
"inviteDescription": "Administrar invitaciones para que otros usuarios se unan a la organización",
@@ -450,6 +455,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 +746,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 +879,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 +903,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 +983,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 +1147,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 +1167,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 +1222,7 @@
"sidebarOverview": "Resumen",
"sidebarHome": "Inicio",
"sidebarSites": "Sitios",
"sidebarApprovals": "Approval Requests",
"sidebarResources": "Recursos",
"sidebarProxyResources": "Público",
"sidebarClientResources": "Privado",
@@ -1204,7 +1239,7 @@
"sidebarIdentityProviders": "Proveedores de identidad",
"sidebarLicense": "Licencia",
"sidebarClients": "Clientes",
"sidebarUserDevices": "Usuarios",
"sidebarUserDevices": "User Devices",
"sidebarMachineClients": "Máquinas",
"sidebarDomains": "Dominios",
"sidebarGeneral": "Gestionar",
@@ -1276,6 +1311,7 @@
"setupErrorCreateAdmin": "Se produjo un error al crear la cuenta de administrador del servidor.",
"certificateStatus": "Estado del certificado",
"loading": "Cargando",
"loadingAnalytics": "Loading Analytics",
"restart": "Reiniciar",
"domains": "Dominios",
"domainsDescription": "Crear y administrar dominios disponibles en la organización",
@@ -1303,6 +1339,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 +1456,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 +1516,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 +1583,8 @@
"IntervalSeconds": "Intervalo Saludable",
"timeoutSeconds": "Tiempo agotado (seg)",
"timeIsInSeconds": "El tiempo está en segundos",
"requireDeviceApproval": "Require Device Approvals",
"requireDeviceApprovalDescription": "Users with this role need new devices approved by an admin before they can connect and 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 +1914,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 +2270,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 +2284,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 +2346,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 +2391,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 +2435,92 @@
"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",
"deviceInformation": "Device Information",
"deviceInformationDescription": "Information about the device and agent",
"platform": "Platform",
"macosVersion": "macOS Version",
"windowsVersion": "Windows Version",
"iosVersion": "iOS Version",
"androidVersion": "Android Version",
"osVersion": "OS Version",
"kernelVersion": "Kernel Version",
"deviceModel": "Device Model",
"serialNumber": "Serial Number",
"hostname": "Hostname",
"firstSeen": "First Seen",
"lastSeen": "Last Seen",
"deviceSettingsDescription": "View device information and settings",
"devicePendingApprovalDescription": "This device is waiting for approval",
"deviceBlockedDescription": "This device is currently blocked. It won't be able to connect to any resources unless unblocked.",
"unblockClient": "Unblock Client",
"unblockClientDescription": "The device has been unblocked",
"unarchiveClient": "Unarchive Client",
"unarchiveClientDescription": "The device has been unarchived",
"block": "Block",
"unblock": "Unblock",
"deviceActions": "Device Actions",
"deviceActionsDescription": "Manage device status and access",
"devicePendingApprovalBannerDescription": "This device is pending approval. It won't be able to connect to resources until approved.",
"connected": "Connected",
"disconnected": "Disconnected",
"approvalsEmptyStateTitle": "Device Approvals Not Enabled",
"approvalsEmptyStateDescription": "Enable device approvals for roles to require admin approval before users can connect new devices.",
"approvalsEmptyStateStep1Title": "Go to Roles",
"approvalsEmptyStateStep1Description": "Navigate to your organization's roles settings to configure device approvals.",
"approvalsEmptyStateStep2Title": "Enable Device Approvals",
"approvalsEmptyStateStep2Description": "Edit a role and enable the 'Require Device Approvals' option. Users with this role will need admin approval for new devices.",
"approvalsEmptyStatePreviewDescription": "Preview: When enabled, pending device requests will appear here for review",
"approvalsEmptyStateButtonText": "Manage Roles"
}
+143 -13
View File
@@ -56,6 +56,9 @@
"sitesBannerTitle": "Se connecter à n'importe quel réseau",
"sitesBannerDescription": "Un site est une connexion à un réseau distant qui permet à Pangolin de fournir aux utilisateurs l'accès à des ressources, publiques ou privées, n'importe où. Installez le connecteur de réseau du site (Newt) partout où vous pouvez exécuter un binaire ou un conteneur pour établir la connexion.",
"sitesBannerButtonText": "Installer le site",
"approvalsBannerTitle": "Approve or Deny Device Access",
"approvalsBannerDescription": "Review and approve or deny device access requests from users. When device approvals are required, users must get admin approval before their devices can connect to your organization's resources.",
"approvalsBannerButtonText": "Learn More",
"siteCreate": "Créer un nœud",
"siteCreateDescription2": "Suivez les étapes ci-dessous pour créer et connecter un nouveau nœud",
"siteCreateDescription": "Créer un nouveau site pour commencer à connecter des ressources",
@@ -257,6 +260,8 @@
"accessRolesSearch": "Chercher des rôles...",
"accessRolesAdd": "Ajouter un rôle",
"accessRoleDelete": "Supprimer le rôle",
"accessApprovalsManage": "Manage Approvals",
"accessApprovalsDescription": "View and manage pending approvals for access to this organization",
"description": "Libellé",
"inviteTitle": "Invitations actives",
"inviteDescription": "Gérer les invitations des autres utilisateurs à rejoindre l'organisation",
@@ -450,6 +455,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 +746,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 +879,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 +903,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 +983,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 +1147,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 +1167,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 +1222,7 @@
"sidebarOverview": "Aperçu",
"sidebarHome": "Domicile",
"sidebarSites": "Nœuds",
"sidebarApprovals": "Approval Requests",
"sidebarResources": "Ressource",
"sidebarProxyResources": "Publique",
"sidebarClientResources": "Privé",
@@ -1204,7 +1239,7 @@
"sidebarIdentityProviders": "Fournisseurs d'identité",
"sidebarLicense": "Licence",
"sidebarClients": "Clients",
"sidebarUserDevices": "Utilisateurs",
"sidebarUserDevices": "User Devices",
"sidebarMachineClients": "Machines",
"sidebarDomains": "Domaines",
"sidebarGeneral": "Gérer",
@@ -1276,6 +1311,7 @@
"setupErrorCreateAdmin": "Une erreur s'est produite lors de la création du compte administrateur du serveur.",
"certificateStatus": "Statut du certificat",
"loading": "Chargement",
"loadingAnalytics": "Loading Analytics",
"restart": "Redémarrer",
"domains": "Domaines",
"domainsDescription": "Créer et gérer les domaines disponibles dans l'organisation",
@@ -1303,6 +1339,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 +1456,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 +1516,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 +1583,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 new devices approved by an admin before they can connect and 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 +1914,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 +2270,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 +2284,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 +2346,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 +2391,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 +2435,92 @@
"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",
"deviceInformation": "Device Information",
"deviceInformationDescription": "Information about the device and agent",
"platform": "Platform",
"macosVersion": "macOS Version",
"windowsVersion": "Windows Version",
"iosVersion": "iOS Version",
"androidVersion": "Android Version",
"osVersion": "OS Version",
"kernelVersion": "Kernel Version",
"deviceModel": "Device Model",
"serialNumber": "Serial Number",
"hostname": "Hostname",
"firstSeen": "First Seen",
"lastSeen": "Last Seen",
"deviceSettingsDescription": "View device information and settings",
"devicePendingApprovalDescription": "This device is waiting for approval",
"deviceBlockedDescription": "This device is currently blocked. It won't be able to connect to any resources unless unblocked.",
"unblockClient": "Unblock Client",
"unblockClientDescription": "The device has been unblocked",
"unarchiveClient": "Unarchive Client",
"unarchiveClientDescription": "The device has been unarchived",
"block": "Block",
"unblock": "Unblock",
"deviceActions": "Device Actions",
"deviceActionsDescription": "Manage device status and access",
"devicePendingApprovalBannerDescription": "This device is pending approval. It won't be able to connect to resources until approved.",
"connected": "Connected",
"disconnected": "Disconnected",
"approvalsEmptyStateTitle": "Device Approvals Not Enabled",
"approvalsEmptyStateDescription": "Enable device approvals for roles to require admin approval before users can connect new devices.",
"approvalsEmptyStateStep1Title": "Go to Roles",
"approvalsEmptyStateStep1Description": "Navigate to your organization's roles settings to configure device approvals.",
"approvalsEmptyStateStep2Title": "Enable Device Approvals",
"approvalsEmptyStateStep2Description": "Edit a role and enable the 'Require Device Approvals' option. Users with this role will need admin approval for new devices.",
"approvalsEmptyStatePreviewDescription": "Preview: When enabled, pending device requests will appear here for review",
"approvalsEmptyStateButtonText": "Manage Roles"
}
+143 -13
View File
@@ -56,6 +56,9 @@
"sitesBannerTitle": "Connetti Qualsiasi Rete",
"sitesBannerDescription": "Un sito è una connessione a una rete remota che consente a Pangolin di fornire accesso alle risorse, pubbliche o private, agli utenti ovunque. Installa il connettore di rete del sito (Newt) ovunque tu possa eseguire un binario o un container per stabilire la connessione.",
"sitesBannerButtonText": "Installa Sito",
"approvalsBannerTitle": "Approve or Deny Device Access",
"approvalsBannerDescription": "Review and approve or deny device access requests from users. When device approvals are required, users must get admin approval before their devices can connect to your organization's resources.",
"approvalsBannerButtonText": "Learn More",
"siteCreate": "Crea Sito",
"siteCreateDescription2": "Segui i passaggi qui sotto per creare e collegare un nuovo sito",
"siteCreateDescription": "Crea un nuovo sito per iniziare a connettere le risorse",
@@ -257,6 +260,8 @@
"accessRolesSearch": "Ricerca ruoli...",
"accessRolesAdd": "Aggiungi Ruolo",
"accessRoleDelete": "Elimina Ruolo",
"accessApprovalsManage": "Manage Approvals",
"accessApprovalsDescription": "View and manage pending approvals for access to this organization",
"description": "Descrizione",
"inviteTitle": "Inviti Aperti",
"inviteDescription": "Gestisci gli inviti per gli altri utenti a unirsi all'organizzazione",
@@ -450,6 +455,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 +746,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 +879,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 +903,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 +983,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 +1147,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 +1167,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 +1222,7 @@
"sidebarOverview": "Panoramica",
"sidebarHome": "Home",
"sidebarSites": "Siti",
"sidebarApprovals": "Approval Requests",
"sidebarResources": "Risorse",
"sidebarProxyResources": "Pubblico",
"sidebarClientResources": "Privato",
@@ -1204,7 +1239,7 @@
"sidebarIdentityProviders": "Fornitori Di Identità",
"sidebarLicense": "Licenza",
"sidebarClients": "Client",
"sidebarUserDevices": "Utenti",
"sidebarUserDevices": "User Devices",
"sidebarMachineClients": "Macchine",
"sidebarDomains": "Domini",
"sidebarGeneral": "Gestisci",
@@ -1276,6 +1311,7 @@
"setupErrorCreateAdmin": "Si è verificato un errore durante la creazione dell'account amministratore del server.",
"certificateStatus": "Stato del Certificato",
"loading": "Caricamento",
"loadingAnalytics": "Loading Analytics",
"restart": "Riavvia",
"domains": "Domini",
"domainsDescription": "Creare e gestire i domini disponibili nell'organizzazione",
@@ -1303,6 +1339,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 +1456,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 +1516,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 +1583,8 @@
"IntervalSeconds": "Intervallo Sano",
"timeoutSeconds": "Timeout (sec)",
"timeIsInSeconds": "Il tempo è in secondi",
"requireDeviceApproval": "Require Device Approvals",
"requireDeviceApprovalDescription": "Users with this role need new devices approved by an admin before they can connect and 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 +1914,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 +2270,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 +2284,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 +2346,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 +2391,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 +2435,92 @@
"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",
"deviceInformation": "Device Information",
"deviceInformationDescription": "Information about the device and agent",
"platform": "Platform",
"macosVersion": "macOS Version",
"windowsVersion": "Windows Version",
"iosVersion": "iOS Version",
"androidVersion": "Android Version",
"osVersion": "OS Version",
"kernelVersion": "Kernel Version",
"deviceModel": "Device Model",
"serialNumber": "Serial Number",
"hostname": "Hostname",
"firstSeen": "First Seen",
"lastSeen": "Last Seen",
"deviceSettingsDescription": "View device information and settings",
"devicePendingApprovalDescription": "This device is waiting for approval",
"deviceBlockedDescription": "This device is currently blocked. It won't be able to connect to any resources unless unblocked.",
"unblockClient": "Unblock Client",
"unblockClientDescription": "The device has been unblocked",
"unarchiveClient": "Unarchive Client",
"unarchiveClientDescription": "The device has been unarchived",
"block": "Block",
"unblock": "Unblock",
"deviceActions": "Device Actions",
"deviceActionsDescription": "Manage device status and access",
"devicePendingApprovalBannerDescription": "This device is pending approval. It won't be able to connect to resources until approved.",
"connected": "Connected",
"disconnected": "Disconnected",
"approvalsEmptyStateTitle": "Device Approvals Not Enabled",
"approvalsEmptyStateDescription": "Enable device approvals for roles to require admin approval before users can connect new devices.",
"approvalsEmptyStateStep1Title": "Go to Roles",
"approvalsEmptyStateStep1Description": "Navigate to your organization's roles settings to configure device approvals.",
"approvalsEmptyStateStep2Title": "Enable Device Approvals",
"approvalsEmptyStateStep2Description": "Edit a role and enable the 'Require Device Approvals' option. Users with this role will need admin approval for new devices.",
"approvalsEmptyStatePreviewDescription": "Preview: When enabled, pending device requests will appear here for review",
"approvalsEmptyStateButtonText": "Manage Roles"
}
+143 -13
View File
@@ -56,6 +56,9 @@
"sitesBannerTitle": "모든 네트워크 연결",
"sitesBannerDescription": "사이트는 원격 네트워크와의 연결로 Pangolin이 어디서나 사용자에게 공공 및 개인 리소스에 대한 접근을 제공할 수 있게 해 줍니다. 연결을 설정하려면 바이너리 또는 컨테이너로 실행할 수 있는 어디서든 사이트 네트워크 커넥터(Newt)를 설치하세요.",
"sitesBannerButtonText": "사이트 설치",
"approvalsBannerTitle": "Approve or Deny Device Access",
"approvalsBannerDescription": "Review and approve or deny device access requests from users. When device approvals are required, users must get admin approval before their devices can connect to your organization's resources.",
"approvalsBannerButtonText": "Learn More",
"siteCreate": "사이트 생성",
"siteCreateDescription2": "아래 단계를 따라 새 사이트를 생성하고 연결하십시오",
"siteCreateDescription": "리소스를 연결하기 위해 새 사이트를 생성하세요.",
@@ -257,6 +260,8 @@
"accessRolesSearch": "역할 검색...",
"accessRolesAdd": "역할 추가",
"accessRoleDelete": "역할 삭제",
"accessApprovalsManage": "Manage Approvals",
"accessApprovalsDescription": "View and manage pending approvals for access to this organization",
"description": "설명",
"inviteTitle": "열린 초대",
"inviteDescription": "다른 사용자가 조직에 참여하도록 초대장을 관리합니다.",
@@ -450,6 +455,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 +746,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 +879,7 @@
"orgPolicyConfig": "조직에 대한 접근을 구성하십시오.",
"idpUpdatedDescription": "아이덴티티 제공자가 성공적으로 업데이트되었습니다",
"redirectUrl": "리디렉션 URL",
"orgIdpRedirectUrls": "리디렉션 URL",
"redirectUrlAbout": "리디렉션 URL에 대한 정보",
"redirectUrlAboutDescription": "사용자가 인증 후 리디렉션될 URL입니다. 이 URL을 신원 제공자 설정에서 구성해야 합니다.",
"pangolinAuth": "인증 - 판골린",
@@ -873,7 +903,7 @@
"inviteAlready": "초대받은 것 같습니다!",
"inviteAlreadyDescription": "초대를 수락하려면 로그인하거나 계정을 생성해야 합니다.",
"signupQuestion": "이미 계정이 있습니까?",
"login": "로그인",
"login": "Log In",
"resourceNotFound": "리소스를 찾을 수 없습니다",
"resourceNotFoundDescription": "접근하려는 리소스가 존재하지 않습니다.",
"pincodeRequirementsLength": "PIN은 정확히 6자리여야 합니다",
@@ -953,13 +983,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 +1147,10 @@
"actionUpdateIdpOrg": "IDP 조직 업데이트",
"actionCreateClient": "클라이언트 생성",
"actionDeleteClient": "클라이언트 삭제",
"actionArchiveClient": "Archive Client",
"actionUnarchiveClient": "Unarchive Client",
"actionBlockClient": "Block Client",
"actionUnblockClient": "Unblock Client",
"actionUpdateClient": "클라이언트 업데이트",
"actionListClients": "클라이언트 목록",
"actionGetClient": "클라이언트 가져오기",
@@ -1133,14 +1167,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 +1222,7 @@
"sidebarOverview": "개요",
"sidebarHome": "홈",
"sidebarSites": "사이트",
"sidebarApprovals": "Approval Requests",
"sidebarResources": "리소스",
"sidebarProxyResources": "공유",
"sidebarClientResources": "비공개",
@@ -1204,7 +1239,7 @@
"sidebarIdentityProviders": "신원 공급자",
"sidebarLicense": "라이선스",
"sidebarClients": "클라이언트",
"sidebarUserDevices": "사용자",
"sidebarUserDevices": "User Devices",
"sidebarMachineClients": "기계",
"sidebarDomains": "도메인",
"sidebarGeneral": "관리",
@@ -1276,6 +1311,7 @@
"setupErrorCreateAdmin": "서버 관리자 계정을 생성하는 동안 오류가 발생했습니다.",
"certificateStatus": "인증서 상태",
"loading": "로딩 중",
"loadingAnalytics": "Loading Analytics",
"restart": "재시작",
"domains": "도메인",
"domainsDescription": "조직에서 사용 가능한 도메인 생성 및 관리",
@@ -1303,6 +1339,7 @@
"refreshError": "데이터 새로고침 실패",
"verified": "검증됨",
"pending": "대기 중",
"pendingApproval": "Pending Approval",
"sidebarBilling": "청구",
"billing": "청구",
"orgBillingDescription": "청구 정보 및 구독을 관리하세요",
@@ -1419,7 +1456,7 @@
"securityKeyRemoveSuccess": "보안 키가 성공적으로 제거되었습니다",
"securityKeyRemoveError": "보안 키 제거 실패",
"securityKeyLoadError": "보안 키를 불러오는 데 실패했습니다",
"securityKeyLogin": "보안 키로 계속하기",
"securityKeyLogin": "Use Security Key",
"securityKeyAuthError": "보안 키를 사용한 인증 실패",
"securityKeyRecommendation": "항상 계정에 액세스할 수 있도록 다른 장치에 백업 보안 키를 등록하세요.",
"registering": "등록 중...",
@@ -1479,7 +1516,7 @@
"IAgreeToThe": "동의합니다",
"termsOfService": "서비스 약관",
"and": "및",
"privacyPolicy": "개인 정보 보호 정책"
"privacyPolicy": "개인 정보 보호 정책."
},
"signUpMarketing": {
"keepMeInTheLoop": "이메일을 통해 소식, 업데이트 및 새로운 기능을 받아보세요."
@@ -1546,6 +1583,8 @@
"IntervalSeconds": "정상 간격",
"timeoutSeconds": "타임아웃(초)",
"timeIsInSeconds": "시간은 초 단위입니다",
"requireDeviceApproval": "Require Device Approvals",
"requireDeviceApprovalDescription": "Users with this role need new devices approved by an admin before they can connect and access resources.",
"retryAttempts": "재시도 횟수",
"expectedResponseCodes": "예상 응답 코드",
"expectedResponseCodesDescription": "정상 상태를 나타내는 HTTP 상태 코드입니다. 비워 두면 200-300이 정상으로 간주됩니다.",
@@ -1875,7 +1914,7 @@
"orgAuthChooseIdpDescription": "계속하려면 신원 공급자를 선택하세요.",
"orgAuthNoIdpConfigured": "이 조직은 구성된 신원 공급자가 없습니다. 대신 Pangolin 아이덴티티로 로그인할 수 있습니다.",
"orgAuthSignInWithPangolin": "Pangolin으로 로그인",
"orgAuthSignInToOrg": "조직에 로그인합니다.",
"orgAuthSignInToOrg": "Sign in to an organization",
"orgAuthSelectOrgTitle": "조직 로그인",
"orgAuthSelectOrgDescription": "계속하려면 조직 ID를 입력하십시오.",
"orgAuthOrgIdPlaceholder": "your-organization",
@@ -2231,6 +2270,8 @@
"deviceCodeInvalidFormat": "코드는 9자리여야 합니다 (예: A1AJ-N5JD)",
"deviceCodeInvalidOrExpired": "무효하거나 만료된 코드",
"deviceCodeVerifyFailed": "이메일 확인에 실패했습니다:",
"deviceCodeValidating": "Validating device code...",
"deviceCodeVerifying": "Verifying device authorization...",
"signedInAs": "로그인한 사용자",
"deviceCodeEnterPrompt": "기기에 표시된 코드를 입력하세요",
"continue": "계속 진행하기",
@@ -2243,7 +2284,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 +2346,7 @@
"identifier": "식별자",
"deviceLoginUseDifferentAccount": "본인이 아닙니까? 다른 계정을 사용하세요.",
"deviceLoginDeviceRequestingAccessToAccount": "장치가 이 계정에 접근하려고 합니다.",
"loginSelectAuthenticationMethod": "Select an authentication method to continue.",
"noData": "데이터 없음",
"machineClients": "기계 클라이언트",
"install": "설치",
@@ -2349,6 +2391,7 @@
"enterConfirmation": "확인 입력",
"blueprintViewDetails": "세부 정보",
"defaultIdentityProvider": "기본 아이덴티티 공급자",
"defaultIdentityProviderDescription": "기본 ID 공급자가 선택되면, 사용자는 인증을 위해 자동으로 해당 공급자로 리디렉션됩니다.",
"editInternalResourceDialogNetworkSettings": "네트워크 설정",
"editInternalResourceDialogAccessPolicy": "액세스 정책",
"editInternalResourceDialogAddRoles": "역할 추가",
@@ -2392,5 +2435,92 @@
"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",
"deviceInformation": "Device Information",
"deviceInformationDescription": "Information about the device and agent",
"platform": "Platform",
"macosVersion": "macOS Version",
"windowsVersion": "Windows Version",
"iosVersion": "iOS Version",
"androidVersion": "Android Version",
"osVersion": "OS Version",
"kernelVersion": "Kernel Version",
"deviceModel": "Device Model",
"serialNumber": "Serial Number",
"hostname": "Hostname",
"firstSeen": "First Seen",
"lastSeen": "Last Seen",
"deviceSettingsDescription": "View device information and settings",
"devicePendingApprovalDescription": "This device is waiting for approval",
"deviceBlockedDescription": "This device is currently blocked. It won't be able to connect to any resources unless unblocked.",
"unblockClient": "Unblock Client",
"unblockClientDescription": "The device has been unblocked",
"unarchiveClient": "Unarchive Client",
"unarchiveClientDescription": "The device has been unarchived",
"block": "Block",
"unblock": "Unblock",
"deviceActions": "Device Actions",
"deviceActionsDescription": "Manage device status and access",
"devicePendingApprovalBannerDescription": "This device is pending approval. It won't be able to connect to resources until approved.",
"connected": "Connected",
"disconnected": "Disconnected",
"approvalsEmptyStateTitle": "Device Approvals Not Enabled",
"approvalsEmptyStateDescription": "Enable device approvals for roles to require admin approval before users can connect new devices.",
"approvalsEmptyStateStep1Title": "Go to Roles",
"approvalsEmptyStateStep1Description": "Navigate to your organization's roles settings to configure device approvals.",
"approvalsEmptyStateStep2Title": "Enable Device Approvals",
"approvalsEmptyStateStep2Description": "Edit a role and enable the 'Require Device Approvals' option. Users with this role will need admin approval for new devices.",
"approvalsEmptyStatePreviewDescription": "Preview: When enabled, pending device requests will appear here for review",
"approvalsEmptyStateButtonText": "Manage Roles"
}
+143 -13
View File
@@ -56,6 +56,9 @@
"sitesBannerTitle": "Koble til alle nettverk",
"sitesBannerDescription": "Et nettverk er en tilkobling til et eksternt nettverk som tillater Pangolin å gi tilgang til ressurser, enten offentlige eller private, til brukere hvor som helst. Installer nettverkskontaktet (Newt) hvor som helst du kan kjøre en binærfil eller container for å opprette forbindelsen.",
"sitesBannerButtonText": "Installer nettsted",
"approvalsBannerTitle": "Approve or Deny Device Access",
"approvalsBannerDescription": "Review and approve or deny device access requests from users. When device approvals are required, users must get admin approval before their devices can connect to your organization's resources.",
"approvalsBannerButtonText": "Learn More",
"siteCreate": "Opprett område",
"siteCreateDescription2": "Følg trinnene nedenfor for å opprette og koble til et nytt område",
"siteCreateDescription": "Opprett et nytt nettsted for å koble til ressurser",
@@ -257,6 +260,8 @@
"accessRolesSearch": "Søk etter roller...",
"accessRolesAdd": "Legg til rolle",
"accessRoleDelete": "Slett rolle",
"accessApprovalsManage": "Manage Approvals",
"accessApprovalsDescription": "View and manage pending approvals for access to this organization",
"description": "Beskrivelse",
"inviteTitle": "Åpne invitasjoner",
"inviteDescription": "Administrer invitasjoner til andre brukere for å bli med i organisasjonen",
@@ -450,6 +455,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 +746,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 +879,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 +903,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 +983,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 +1147,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 +1167,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 +1222,7 @@
"sidebarOverview": "Oversikt",
"sidebarHome": "Hjem",
"sidebarSites": "Områder",
"sidebarApprovals": "Approval Requests",
"sidebarResources": "Ressurser",
"sidebarProxyResources": "Offentlig",
"sidebarClientResources": "Privat",
@@ -1204,7 +1239,7 @@
"sidebarIdentityProviders": "Identitetsleverandører",
"sidebarLicense": "Lisens",
"sidebarClients": "Klienter",
"sidebarUserDevices": "Brukere",
"sidebarUserDevices": "User Devices",
"sidebarMachineClients": "Maskiner",
"sidebarDomains": "Domener",
"sidebarGeneral": "Administrer",
@@ -1276,6 +1311,7 @@
"setupErrorCreateAdmin": "En feil oppstod under opprettelsen av serveradministratorkontoen.",
"certificateStatus": "Sertifikatstatus",
"loading": "Laster inn",
"loadingAnalytics": "Loading Analytics",
"restart": "Start på nytt",
"domains": "Domener",
"domainsDescription": "Opprett og behandle domener som er tilgjengelige i organisasjonen",
@@ -1303,6 +1339,7 @@
"refreshError": "Klarte ikke å oppdatere data",
"verified": "Verifisert",
"pending": "Venter",
"pendingApproval": "Pending Approval",
"sidebarBilling": "Fakturering",
"billing": "Fakturering",
"orgBillingDescription": "Administrer faktureringsinformasjon og abonnementer",
@@ -1419,7 +1456,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 +1516,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 +1583,8 @@
"IntervalSeconds": "Sunt intervall",
"timeoutSeconds": "Tidsavbrudd (sek)",
"timeIsInSeconds": "Tid er i sekunder",
"requireDeviceApproval": "Require Device Approvals",
"requireDeviceApprovalDescription": "Users with this role need new devices approved by an admin before they can connect and 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 +1914,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 +2270,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 +2284,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 +2346,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 +2391,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 +2435,92 @@
"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",
"deviceInformation": "Device Information",
"deviceInformationDescription": "Information about the device and agent",
"platform": "Platform",
"macosVersion": "macOS Version",
"windowsVersion": "Windows Version",
"iosVersion": "iOS Version",
"androidVersion": "Android Version",
"osVersion": "OS Version",
"kernelVersion": "Kernel Version",
"deviceModel": "Device Model",
"serialNumber": "Serial Number",
"hostname": "Hostname",
"firstSeen": "First Seen",
"lastSeen": "Last Seen",
"deviceSettingsDescription": "View device information and settings",
"devicePendingApprovalDescription": "This device is waiting for approval",
"deviceBlockedDescription": "This device is currently blocked. It won't be able to connect to any resources unless unblocked.",
"unblockClient": "Unblock Client",
"unblockClientDescription": "The device has been unblocked",
"unarchiveClient": "Unarchive Client",
"unarchiveClientDescription": "The device has been unarchived",
"block": "Block",
"unblock": "Unblock",
"deviceActions": "Device Actions",
"deviceActionsDescription": "Manage device status and access",
"devicePendingApprovalBannerDescription": "This device is pending approval. It won't be able to connect to resources until approved.",
"connected": "Connected",
"disconnected": "Disconnected",
"approvalsEmptyStateTitle": "Device Approvals Not Enabled",
"approvalsEmptyStateDescription": "Enable device approvals for roles to require admin approval before users can connect new devices.",
"approvalsEmptyStateStep1Title": "Go to Roles",
"approvalsEmptyStateStep1Description": "Navigate to your organization's roles settings to configure device approvals.",
"approvalsEmptyStateStep2Title": "Enable Device Approvals",
"approvalsEmptyStateStep2Description": "Edit a role and enable the 'Require Device Approvals' option. Users with this role will need admin approval for new devices.",
"approvalsEmptyStatePreviewDescription": "Preview: When enabled, pending device requests will appear here for review",
"approvalsEmptyStateButtonText": "Manage Roles"
}
+143 -13
View File
@@ -56,6 +56,9 @@
"sitesBannerTitle": "Verbind elk netwerk",
"sitesBannerDescription": "Een site is een verbinding met een extern netwerk waarmee Pangolin toegang biedt tot bronnen, zowel openbaar als privé, aan gebruikers overal. Installeer de sitedatacenterconnector (Newt) overal waar je een binaire of container kunt uitvoeren om de verbinding tot stand te brengen.",
"sitesBannerButtonText": "Site installeren",
"approvalsBannerTitle": "Approve or Deny Device Access",
"approvalsBannerDescription": "Review and approve or deny device access requests from users. When device approvals are required, users must get admin approval before their devices can connect to your organization's resources.",
"approvalsBannerButtonText": "Learn More",
"siteCreate": "Site maken",
"siteCreateDescription2": "Volg de onderstaande stappen om een nieuwe site aan te maken en te verbinden",
"siteCreateDescription": "Maak een nieuwe site aan om bronnen te verbinden",
@@ -257,6 +260,8 @@
"accessRolesSearch": "Rollen zoeken...",
"accessRolesAdd": "Rol toevoegen",
"accessRoleDelete": "Verwijder rol",
"accessApprovalsManage": "Manage Approvals",
"accessApprovalsDescription": "View and manage pending approvals for access to this organization",
"description": "Beschrijving",
"inviteTitle": "Open uitnodigingen",
"inviteDescription": "Beheer uitnodigingen voor andere gebruikers om deel te nemen aan de organisatie",
@@ -450,6 +455,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 +746,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 +879,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 +903,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 +983,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 +1147,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 +1167,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 +1222,7 @@
"sidebarOverview": "Overzicht.",
"sidebarHome": "Startpagina",
"sidebarSites": "Werkruimtes",
"sidebarApprovals": "Approval Requests",
"sidebarResources": "Bronnen",
"sidebarProxyResources": "Openbaar",
"sidebarClientResources": "Privé",
@@ -1204,7 +1239,7 @@
"sidebarIdentityProviders": "Identiteit aanbieders",
"sidebarLicense": "Licentie",
"sidebarClients": "Clienten",
"sidebarUserDevices": "Gebruikers",
"sidebarUserDevices": "User Devices",
"sidebarMachineClients": "Machines",
"sidebarDomains": "Domeinen",
"sidebarGeneral": "Beheren",
@@ -1276,6 +1311,7 @@
"setupErrorCreateAdmin": "Er is een fout opgetreden bij het maken van het serverbeheerdersaccount.",
"certificateStatus": "Certificaatstatus",
"loading": "Bezig met laden",
"loadingAnalytics": "Loading Analytics",
"restart": "Herstarten",
"domains": "Domeinen",
"domainsDescription": "Maak en beheer domeinen die beschikbaar zijn in de organisatie",
@@ -1303,6 +1339,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 +1456,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 +1516,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 +1583,8 @@
"IntervalSeconds": "Gezonde Interval",
"timeoutSeconds": "Timeout (sec)",
"timeIsInSeconds": "Tijd is in seconden",
"requireDeviceApproval": "Require Device Approvals",
"requireDeviceApprovalDescription": "Users with this role need new devices approved by an admin before they can connect and 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 +1914,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 +2270,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 +2284,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 +2346,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 +2391,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 +2435,92 @@
"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",
"deviceInformation": "Device Information",
"deviceInformationDescription": "Information about the device and agent",
"platform": "Platform",
"macosVersion": "macOS Version",
"windowsVersion": "Windows Version",
"iosVersion": "iOS Version",
"androidVersion": "Android Version",
"osVersion": "OS Version",
"kernelVersion": "Kernel Version",
"deviceModel": "Device Model",
"serialNumber": "Serial Number",
"hostname": "Hostname",
"firstSeen": "First Seen",
"lastSeen": "Last Seen",
"deviceSettingsDescription": "View device information and settings",
"devicePendingApprovalDescription": "This device is waiting for approval",
"deviceBlockedDescription": "This device is currently blocked. It won't be able to connect to any resources unless unblocked.",
"unblockClient": "Unblock Client",
"unblockClientDescription": "The device has been unblocked",
"unarchiveClient": "Unarchive Client",
"unarchiveClientDescription": "The device has been unarchived",
"block": "Block",
"unblock": "Unblock",
"deviceActions": "Device Actions",
"deviceActionsDescription": "Manage device status and access",
"devicePendingApprovalBannerDescription": "This device is pending approval. It won't be able to connect to resources until approved.",
"connected": "Connected",
"disconnected": "Disconnected",
"approvalsEmptyStateTitle": "Device Approvals Not Enabled",
"approvalsEmptyStateDescription": "Enable device approvals for roles to require admin approval before users can connect new devices.",
"approvalsEmptyStateStep1Title": "Go to Roles",
"approvalsEmptyStateStep1Description": "Navigate to your organization's roles settings to configure device approvals.",
"approvalsEmptyStateStep2Title": "Enable Device Approvals",
"approvalsEmptyStateStep2Description": "Edit a role and enable the 'Require Device Approvals' option. Users with this role will need admin approval for new devices.",
"approvalsEmptyStatePreviewDescription": "Preview: When enabled, pending device requests will appear here for review",
"approvalsEmptyStateButtonText": "Manage Roles"
}
+143 -13
View File
@@ -56,6 +56,9 @@
"sitesBannerTitle": "Połącz dowolną sieć",
"sitesBannerDescription": "Witryna to połączenie z siecią zdalną, które umożliwia Pangolinowi zapewnienie dostępu do zasobów, publicznych lub prywatnych, użytkownikom w dowolnym miejscu. Zainstaluj łącznik sieci w witrynie (Newt) w dowolnym miejscu, w którym możesz uruchomić binarkę lub kontener, aby ustanowić połączenie.",
"sitesBannerButtonText": "Zainstaluj witrynę",
"approvalsBannerTitle": "Approve or Deny Device Access",
"approvalsBannerDescription": "Review and approve or deny device access requests from users. When device approvals are required, users must get admin approval before their devices can connect to your organization's resources.",
"approvalsBannerButtonText": "Learn More",
"siteCreate": "Utwórz witrynę",
"siteCreateDescription2": "Wykonaj poniższe kroki, aby utworzyć i połączyć nową witrynę",
"siteCreateDescription": "Utwórz nową witrynę, aby rozpocząć łączenie zasobów",
@@ -257,6 +260,8 @@
"accessRolesSearch": "Szukaj ról...",
"accessRolesAdd": "Dodaj rolę",
"accessRoleDelete": "Usuń rolę",
"accessApprovalsManage": "Manage Approvals",
"accessApprovalsDescription": "View and manage pending approvals for access to this organization",
"description": "Opis",
"inviteTitle": "Otwórz zaproszenia",
"inviteDescription": "Zarządzaj zaproszeniami dla innych użytkowników do dołączenia do organizacji",
@@ -450,6 +455,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 +746,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 +879,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 +903,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 +983,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 +1147,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 +1167,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 +1222,7 @@
"sidebarOverview": "Przegląd",
"sidebarHome": "Strona główna",
"sidebarSites": "Witryny",
"sidebarApprovals": "Approval Requests",
"sidebarResources": "Zasoby",
"sidebarProxyResources": "Publiczne",
"sidebarClientResources": "Prywatny",
@@ -1204,7 +1239,7 @@
"sidebarIdentityProviders": "Dostawcy tożsamości",
"sidebarLicense": "Licencja",
"sidebarClients": "Klienty",
"sidebarUserDevices": "Użytkownicy",
"sidebarUserDevices": "User Devices",
"sidebarMachineClients": "Maszyny",
"sidebarDomains": "Domeny",
"sidebarGeneral": "Zarządzaj",
@@ -1276,6 +1311,7 @@
"setupErrorCreateAdmin": "Wystąpił błąd podczas tworzenia konta administratora serwera.",
"certificateStatus": "Status certyfikatu",
"loading": "Ładowanie",
"loadingAnalytics": "Loading Analytics",
"restart": "Uruchom ponownie",
"domains": "Domeny",
"domainsDescription": "Tworzenie domen dostępnych w organizacji i zarządzanie nimi",
@@ -1303,6 +1339,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 +1456,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 +1516,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 +1583,8 @@
"IntervalSeconds": "Interwał Zdrowy",
"timeoutSeconds": "Limit czasu (sek)",
"timeIsInSeconds": "Czas w sekundach",
"requireDeviceApproval": "Require Device Approvals",
"requireDeviceApprovalDescription": "Users with this role need new devices approved by an admin before they can connect and 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 +1914,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 +2270,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 +2284,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 +2346,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 +2391,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 +2435,92 @@
"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",
"deviceInformation": "Device Information",
"deviceInformationDescription": "Information about the device and agent",
"platform": "Platform",
"macosVersion": "macOS Version",
"windowsVersion": "Windows Version",
"iosVersion": "iOS Version",
"androidVersion": "Android Version",
"osVersion": "OS Version",
"kernelVersion": "Kernel Version",
"deviceModel": "Device Model",
"serialNumber": "Serial Number",
"hostname": "Hostname",
"firstSeen": "First Seen",
"lastSeen": "Last Seen",
"deviceSettingsDescription": "View device information and settings",
"devicePendingApprovalDescription": "This device is waiting for approval",
"deviceBlockedDescription": "This device is currently blocked. It won't be able to connect to any resources unless unblocked.",
"unblockClient": "Unblock Client",
"unblockClientDescription": "The device has been unblocked",
"unarchiveClient": "Unarchive Client",
"unarchiveClientDescription": "The device has been unarchived",
"block": "Block",
"unblock": "Unblock",
"deviceActions": "Device Actions",
"deviceActionsDescription": "Manage device status and access",
"devicePendingApprovalBannerDescription": "This device is pending approval. It won't be able to connect to resources until approved.",
"connected": "Connected",
"disconnected": "Disconnected",
"approvalsEmptyStateTitle": "Device Approvals Not Enabled",
"approvalsEmptyStateDescription": "Enable device approvals for roles to require admin approval before users can connect new devices.",
"approvalsEmptyStateStep1Title": "Go to Roles",
"approvalsEmptyStateStep1Description": "Navigate to your organization's roles settings to configure device approvals.",
"approvalsEmptyStateStep2Title": "Enable Device Approvals",
"approvalsEmptyStateStep2Description": "Edit a role and enable the 'Require Device Approvals' option. Users with this role will need admin approval for new devices.",
"approvalsEmptyStatePreviewDescription": "Preview: When enabled, pending device requests will appear here for review",
"approvalsEmptyStateButtonText": "Manage Roles"
}
+143 -13
View File
@@ -56,6 +56,9 @@
"sitesBannerTitle": "Conectar a Qualquer Rede",
"sitesBannerDescription": "Um site é uma conexão a uma rede remota que permite ao Pangolin fornecer acesso a recursos, sejam eles públicos ou privados, a usuários em qualquer lugar. Instale o conector de rede do site (Newt) em qualquer lugar onde você possa executar um binário ou contêiner para estabelecer a conexão.",
"sitesBannerButtonText": "Instalar Site",
"approvalsBannerTitle": "Approve or Deny Device Access",
"approvalsBannerDescription": "Review and approve or deny device access requests from users. When device approvals are required, users must get admin approval before their devices can connect to your organization's resources.",
"approvalsBannerButtonText": "Learn More",
"siteCreate": "Criar site",
"siteCreateDescription2": "Siga os passos abaixo para criar e conectar um novo site",
"siteCreateDescription": "Crie um novo site para começar a conectar os recursos",
@@ -257,6 +260,8 @@
"accessRolesSearch": "Pesquisar funções...",
"accessRolesAdd": "Adicionar função",
"accessRoleDelete": "Excluir Papel",
"accessApprovalsManage": "Manage Approvals",
"accessApprovalsDescription": "View and manage pending approvals for access to this organization",
"description": "Descrição:",
"inviteTitle": "Convites Abertos",
"inviteDescription": "Gerenciar convites para outros usuários participarem da organização",
@@ -450,6 +455,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 +746,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 +879,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 +903,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 +983,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 +1147,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 +1167,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 +1222,7 @@
"sidebarOverview": "Geral",
"sidebarHome": "Residencial",
"sidebarSites": "sites",
"sidebarApprovals": "Approval Requests",
"sidebarResources": "Recursos",
"sidebarProxyResources": "Público",
"sidebarClientResources": "Privado",
@@ -1204,7 +1239,7 @@
"sidebarIdentityProviders": "Provedores de identidade",
"sidebarLicense": "Tipo:",
"sidebarClients": "Clientes",
"sidebarUserDevices": "Utilizadores",
"sidebarUserDevices": "User Devices",
"sidebarMachineClients": "Máquinas",
"sidebarDomains": "Domínios",
"sidebarGeneral": "Gerir",
@@ -1276,6 +1311,7 @@
"setupErrorCreateAdmin": "Ocorreu um erro ao criar a conta de administrador do servidor.",
"certificateStatus": "Status do Certificado",
"loading": "Carregando",
"loadingAnalytics": "Loading Analytics",
"restart": "Reiniciar",
"domains": "Domínios",
"domainsDescription": "Criar e gerenciar domínios disponíveis na organização",
@@ -1303,6 +1339,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 +1456,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 +1516,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 +1583,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 new devices approved by an admin before they can connect and 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 +1914,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 +2270,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 +2284,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 +2346,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 +2391,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 +2435,92 @@
"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",
"deviceInformation": "Device Information",
"deviceInformationDescription": "Information about the device and agent",
"platform": "Platform",
"macosVersion": "macOS Version",
"windowsVersion": "Windows Version",
"iosVersion": "iOS Version",
"androidVersion": "Android Version",
"osVersion": "OS Version",
"kernelVersion": "Kernel Version",
"deviceModel": "Device Model",
"serialNumber": "Serial Number",
"hostname": "Hostname",
"firstSeen": "First Seen",
"lastSeen": "Last Seen",
"deviceSettingsDescription": "View device information and settings",
"devicePendingApprovalDescription": "This device is waiting for approval",
"deviceBlockedDescription": "This device is currently blocked. It won't be able to connect to any resources unless unblocked.",
"unblockClient": "Unblock Client",
"unblockClientDescription": "The device has been unblocked",
"unarchiveClient": "Unarchive Client",
"unarchiveClientDescription": "The device has been unarchived",
"block": "Block",
"unblock": "Unblock",
"deviceActions": "Device Actions",
"deviceActionsDescription": "Manage device status and access",
"devicePendingApprovalBannerDescription": "This device is pending approval. It won't be able to connect to resources until approved.",
"connected": "Connected",
"disconnected": "Disconnected",
"approvalsEmptyStateTitle": "Device Approvals Not Enabled",
"approvalsEmptyStateDescription": "Enable device approvals for roles to require admin approval before users can connect new devices.",
"approvalsEmptyStateStep1Title": "Go to Roles",
"approvalsEmptyStateStep1Description": "Navigate to your organization's roles settings to configure device approvals.",
"approvalsEmptyStateStep2Title": "Enable Device Approvals",
"approvalsEmptyStateStep2Description": "Edit a role and enable the 'Require Device Approvals' option. Users with this role will need admin approval for new devices.",
"approvalsEmptyStatePreviewDescription": "Preview: When enabled, pending device requests will appear here for review",
"approvalsEmptyStateButtonText": "Manage Roles"
}
+143 -13
View File
@@ -56,6 +56,9 @@
"sitesBannerTitle": "Подключить любую сеть",
"sitesBannerDescription": "Сайт — это соединение с удаленной сетью, которое позволяет Pangolin предоставлять доступ к ресурсам, будь они общедоступными или частными, пользователям в любом месте. Установите сетевой коннектор сайта (Newt) там, где можно запустить исполняемый файл или контейнер, чтобы установить соединение.",
"sitesBannerButtonText": "Установить сайт",
"approvalsBannerTitle": "Approve or Deny Device Access",
"approvalsBannerDescription": "Review and approve or deny device access requests from users. When device approvals are required, users must get admin approval before their devices can connect to your organization's resources.",
"approvalsBannerButtonText": "Learn More",
"siteCreate": "Создать сайт",
"siteCreateDescription2": "Следуйте инструкциям ниже для создания и подключения нового сайта",
"siteCreateDescription": "Создайте новый сайт для начала подключения ресурсов",
@@ -257,6 +260,8 @@
"accessRolesSearch": "Поиск ролей...",
"accessRolesAdd": "Добавить роль",
"accessRoleDelete": "Удалить роль",
"accessApprovalsManage": "Manage Approvals",
"accessApprovalsDescription": "View and manage pending approvals for access to this organization",
"description": "Описание",
"inviteTitle": "Открытые приглашения",
"inviteDescription": "Управление приглашениями для присоединения других пользователей к организации",
@@ -450,6 +455,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 +746,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 +879,7 @@
"orgPolicyConfig": "Настроить доступ для организации",
"idpUpdatedDescription": "Поставщик удостоверений успешно обновлён",
"redirectUrl": "URL редиректа",
"orgIdpRedirectUrls": "Перенаправление URL",
"redirectUrlAbout": "О редиректе URL",
"redirectUrlAboutDescription": "Это URL, на который пользователи будут перенаправлены после аутентификации. Вам нужно настроить этот URL в настройках провайдера.",
"pangolinAuth": "Аутентификация - Pangolin",
@@ -873,7 +903,7 @@
"inviteAlready": "Похоже, вы были приглашены!",
"inviteAlreadyDescription": "Чтобы принять приглашение, вы должны войти или создать учётную запись.",
"signupQuestion": "Уже есть учётная запись?",
"login": "Войти",
"login": "Log In",
"resourceNotFound": "Ресурс не найден",
"resourceNotFoundDescription": "Ресурс, к которому вы пытаетесь получить доступ, не существует.",
"pincodeRequirementsLength": "PIN должен состоять ровно из 6 цифр",
@@ -953,13 +983,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 +1147,10 @@
"actionUpdateIdpOrg": "Обновить организацию IDP",
"actionCreateClient": "Создать Клиента",
"actionDeleteClient": "Удалить Клиента",
"actionArchiveClient": "Archive Client",
"actionUnarchiveClient": "Unarchive Client",
"actionBlockClient": "Block Client",
"actionUnblockClient": "Unblock Client",
"actionUpdateClient": "Обновить Клиента",
"actionListClients": "Список Клиентов",
"actionGetClient": "Получить Клиента",
@@ -1133,14 +1167,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 +1222,7 @@
"sidebarOverview": "Обзор",
"sidebarHome": "Главная",
"sidebarSites": "Сайты",
"sidebarApprovals": "Approval Requests",
"sidebarResources": "Ресурсы",
"sidebarProxyResources": "Публичный",
"sidebarClientResources": "Приватный",
@@ -1204,7 +1239,7 @@
"sidebarIdentityProviders": "Поставщики удостоверений",
"sidebarLicense": "Лицензия",
"sidebarClients": "Клиенты",
"sidebarUserDevices": "Пользователи",
"sidebarUserDevices": "User Devices",
"sidebarMachineClients": "Машины",
"sidebarDomains": "Домены",
"sidebarGeneral": "Управление",
@@ -1276,6 +1311,7 @@
"setupErrorCreateAdmin": "Произошла ошибка при создании учётной записи администратора сервера.",
"certificateStatus": "Статус сертификата",
"loading": "Загрузка",
"loadingAnalytics": "Loading Analytics",
"restart": "Перезагрузка",
"domains": "Домены",
"domainsDescription": "Создание и управление доменами, доступными в организации",
@@ -1303,6 +1339,7 @@
"refreshError": "Не удалось обновить данные",
"verified": "Подтверждено",
"pending": "В ожидании",
"pendingApproval": "Pending Approval",
"sidebarBilling": "Выставление счетов",
"billing": "Выставление счетов",
"orgBillingDescription": "Управление платежной информацией и подписками",
@@ -1419,7 +1456,7 @@
"securityKeyRemoveSuccess": "Ключ безопасности успешно удален",
"securityKeyRemoveError": "Не удалось удалить ключ безопасности",
"securityKeyLoadError": "Не удалось загрузить ключи безопасности",
"securityKeyLogin": "Продолжить с ключом безопасности",
"securityKeyLogin": "Use Security Key",
"securityKeyAuthError": "Не удалось аутентифицироваться с ключом безопасности",
"securityKeyRecommendation": "Зарегистрируйте резервный ключ безопасности на другом устройстве, чтобы всегда иметь доступ к вашему аккаунту.",
"registering": "Регистрация...",
@@ -1479,7 +1516,7 @@
"IAgreeToThe": "Я согласен с",
"termsOfService": "условия использования",
"and": "и",
"privacyPolicy": "политика конфиденциальности"
"privacyPolicy": "политика конфиденциальности."
},
"signUpMarketing": {
"keepMeInTheLoop": "Держите меня в цикле с новостями, обновлениями и новыми функциями по электронной почте."
@@ -1546,6 +1583,8 @@
"IntervalSeconds": "Интервал здоровых состояний",
"timeoutSeconds": "Таймаут (сек)",
"timeIsInSeconds": "Время указано в секундах",
"requireDeviceApproval": "Require Device Approvals",
"requireDeviceApprovalDescription": "Users with this role need new devices approved by an admin before they can connect and access resources.",
"retryAttempts": "Количество попыток повторного запроса",
"expectedResponseCodes": "Ожидаемые коды ответов",
"expectedResponseCodesDescription": "HTTP-код состояния, указывающий на здоровое состояние. Если оставить пустым, 200-300 считается здоровым.",
@@ -1875,7 +1914,7 @@
"orgAuthChooseIdpDescription": "Выберите своего поставщика удостоверений личности для продолжения",
"orgAuthNoIdpConfigured": "Эта организация не имеет настроенных поставщиков идентификационных данных. Вместо этого вы можете войти в свой Pangolin.",
"orgAuthSignInWithPangolin": "Войти через Pangolin",
"orgAuthSignInToOrg": "Войдите в организацию",
"orgAuthSignInToOrg": "Sign in to an organization",
"orgAuthSelectOrgTitle": "Вход в организацию",
"orgAuthSelectOrgDescription": "Введите ID вашей организации, чтобы продолжить",
"orgAuthOrgIdPlaceholder": "ваша-организация",
@@ -2231,6 +2270,8 @@
"deviceCodeInvalidFormat": "Код должен быть 9 символов (например, A1AJ-N5JD)",
"deviceCodeInvalidOrExpired": "Неверный или просроченный код",
"deviceCodeVerifyFailed": "Не удалось проверить код устройства",
"deviceCodeValidating": "Validating device code...",
"deviceCodeVerifying": "Verifying device authorization...",
"signedInAs": "Вы вошли как",
"deviceCodeEnterPrompt": "Введите код, отображаемый на устройстве",
"continue": "Продолжить",
@@ -2243,7 +2284,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 +2346,7 @@
"identifier": "Identifier",
"deviceLoginUseDifferentAccount": "Не вы? Используйте другую учетную запись.",
"deviceLoginDeviceRequestingAccessToAccount": "Устройство запрашивает доступ к этой учетной записи.",
"loginSelectAuthenticationMethod": "Select an authentication method to continue.",
"noData": "Нет данных",
"machineClients": "Машинные клиенты",
"install": "Установить",
@@ -2349,6 +2391,7 @@
"enterConfirmation": "Введите подтверждение",
"blueprintViewDetails": "Подробности",
"defaultIdentityProvider": "Поставщик удостоверений по умолчанию",
"defaultIdentityProviderDescription": "Когда выбран поставщик идентификации по умолчанию, пользователь будет автоматически перенаправлен на провайдер для аутентификации.",
"editInternalResourceDialogNetworkSettings": "Настройки сети",
"editInternalResourceDialogAccessPolicy": "Политика доступа",
"editInternalResourceDialogAddRoles": "Добавить роли",
@@ -2392,5 +2435,92 @@
"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",
"deviceInformation": "Device Information",
"deviceInformationDescription": "Information about the device and agent",
"platform": "Platform",
"macosVersion": "macOS Version",
"windowsVersion": "Windows Version",
"iosVersion": "iOS Version",
"androidVersion": "Android Version",
"osVersion": "OS Version",
"kernelVersion": "Kernel Version",
"deviceModel": "Device Model",
"serialNumber": "Serial Number",
"hostname": "Hostname",
"firstSeen": "First Seen",
"lastSeen": "Last Seen",
"deviceSettingsDescription": "View device information and settings",
"devicePendingApprovalDescription": "This device is waiting for approval",
"deviceBlockedDescription": "This device is currently blocked. It won't be able to connect to any resources unless unblocked.",
"unblockClient": "Unblock Client",
"unblockClientDescription": "The device has been unblocked",
"unarchiveClient": "Unarchive Client",
"unarchiveClientDescription": "The device has been unarchived",
"block": "Block",
"unblock": "Unblock",
"deviceActions": "Device Actions",
"deviceActionsDescription": "Manage device status and access",
"devicePendingApprovalBannerDescription": "This device is pending approval. It won't be able to connect to resources until approved.",
"connected": "Connected",
"disconnected": "Disconnected",
"approvalsEmptyStateTitle": "Device Approvals Not Enabled",
"approvalsEmptyStateDescription": "Enable device approvals for roles to require admin approval before users can connect new devices.",
"approvalsEmptyStateStep1Title": "Go to Roles",
"approvalsEmptyStateStep1Description": "Navigate to your organization's roles settings to configure device approvals.",
"approvalsEmptyStateStep2Title": "Enable Device Approvals",
"approvalsEmptyStateStep2Description": "Edit a role and enable the 'Require Device Approvals' option. Users with this role will need admin approval for new devices.",
"approvalsEmptyStatePreviewDescription": "Preview: When enabled, pending device requests will appear here for review",
"approvalsEmptyStateButtonText": "Manage Roles"
}
+143 -13
View File
@@ -56,6 +56,9 @@
"sitesBannerTitle": "Herhangi Bir Ağa Bağlan",
"sitesBannerDescription": "Bir site, Pangolin'in kullanıcılara, halka açık veya özel kaynaklara, her yerden erişim sağlamak için uzak bir ağa bağlantı sunmasıdır. Site ağı bağlantısını (Newt) çalıştırabileceğiniz her yere kurarak bağlantıyı kurunuz.",
"sitesBannerButtonText": "Site Kur",
"approvalsBannerTitle": "Approve or Deny Device Access",
"approvalsBannerDescription": "Review and approve or deny device access requests from users. When device approvals are required, users must get admin approval before their devices can connect to your organization's resources.",
"approvalsBannerButtonText": "Learn More",
"siteCreate": "Site Oluştur",
"siteCreateDescription2": "Yeni bir site oluşturup bağlanmak için aşağıdaki adımları izleyin",
"siteCreateDescription": "Kaynaklarınızı bağlamaya başlamak için yeni bir site oluşturun",
@@ -257,6 +260,8 @@
"accessRolesSearch": "Rolleri ara...",
"accessRolesAdd": "Rol Ekle",
"accessRoleDelete": "Rolü Sil",
"accessApprovalsManage": "Manage Approvals",
"accessApprovalsDescription": "View and manage pending approvals for access to this 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 +455,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 +746,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 +879,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 +903,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 +983,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 +1147,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 +1167,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 +1222,7 @@
"sidebarOverview": "Genel Bakış",
"sidebarHome": "Ana Sayfa",
"sidebarSites": "Siteler",
"sidebarApprovals": "Approval Requests",
"sidebarResources": "Kaynaklar",
"sidebarProxyResources": "Herkese Açık",
"sidebarClientResources": "Özel",
@@ -1204,7 +1239,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",
@@ -1276,6 +1311,7 @@
"setupErrorCreateAdmin": "Sunucu yönetici hesabı oluşturulurken bir hata oluştu.",
"certificateStatus": "Sertifika Durumu",
"loading": "Yükleniyor",
"loadingAnalytics": "Loading Analytics",
"restart": "Yeniden Başlat",
"domains": "Alan Adları",
"domainsDescription": "Organizasyonda kullanılabilir alan adlarını oluşturun ve yönetin",
@@ -1303,6 +1339,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 +1456,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 +1516,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 +1583,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 new devices approved by an admin before they can connect and 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 +1914,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 +2270,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 +2284,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 +2346,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 +2391,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 +2435,92 @@
"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",
"deviceInformation": "Device Information",
"deviceInformationDescription": "Information about the device and agent",
"platform": "Platform",
"macosVersion": "macOS Version",
"windowsVersion": "Windows Version",
"iosVersion": "iOS Version",
"androidVersion": "Android Version",
"osVersion": "OS Version",
"kernelVersion": "Kernel Version",
"deviceModel": "Device Model",
"serialNumber": "Serial Number",
"hostname": "Hostname",
"firstSeen": "First Seen",
"lastSeen": "Last Seen",
"deviceSettingsDescription": "View device information and settings",
"devicePendingApprovalDescription": "This device is waiting for approval",
"deviceBlockedDescription": "This device is currently blocked. It won't be able to connect to any resources unless unblocked.",
"unblockClient": "Unblock Client",
"unblockClientDescription": "The device has been unblocked",
"unarchiveClient": "Unarchive Client",
"unarchiveClientDescription": "The device has been unarchived",
"block": "Block",
"unblock": "Unblock",
"deviceActions": "Device Actions",
"deviceActionsDescription": "Manage device status and access",
"devicePendingApprovalBannerDescription": "This device is pending approval. It won't be able to connect to resources until approved.",
"connected": "Connected",
"disconnected": "Disconnected",
"approvalsEmptyStateTitle": "Device Approvals Not Enabled",
"approvalsEmptyStateDescription": "Enable device approvals for roles to require admin approval before users can connect new devices.",
"approvalsEmptyStateStep1Title": "Go to Roles",
"approvalsEmptyStateStep1Description": "Navigate to your organization's roles settings to configure device approvals.",
"approvalsEmptyStateStep2Title": "Enable Device Approvals",
"approvalsEmptyStateStep2Description": "Edit a role and enable the 'Require Device Approvals' option. Users with this role will need admin approval for new devices.",
"approvalsEmptyStatePreviewDescription": "Preview: When enabled, pending device requests will appear here for review",
"approvalsEmptyStateButtonText": "Manage Roles"
}
+143 -13
View File
@@ -56,6 +56,9 @@
"sitesBannerTitle": "连接任何网络",
"sitesBannerDescription": "站点是连接到远程网络的链接,允许Pangolin为用户提供资源访问,无论是公共还是私人。可以在任何可以运行二进制文件或容器的地方安装站点网络连接器(Newt)以建立连接。",
"sitesBannerButtonText": "安装站点",
"approvalsBannerTitle": "Approve or Deny Device Access",
"approvalsBannerDescription": "Review and approve or deny device access requests from users. When device approvals are required, users must get admin approval before their devices can connect to your organization's resources.",
"approvalsBannerButtonText": "Learn More",
"siteCreate": "创建站点",
"siteCreateDescription2": "按照下面的步骤创建和连接一个新站点",
"siteCreateDescription": "创建一个新站点开始连接资源",
@@ -257,6 +260,8 @@
"accessRolesSearch": "搜索角色...",
"accessRolesAdd": "添加角色",
"accessRoleDelete": "删除角色",
"accessApprovalsManage": "Manage Approvals",
"accessApprovalsDescription": "View and manage pending approvals for access to this organization",
"description": "描述",
"inviteTitle": "打开邀请",
"inviteDescription": "管理其他用户加入机构的邀请",
@@ -450,6 +455,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 +746,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 +879,7 @@
"orgPolicyConfig": "配置组织访问权限",
"idpUpdatedDescription": "身份提供商更新成功",
"redirectUrl": "重定向网址",
"orgIdpRedirectUrls": "重定向URL",
"redirectUrlAbout": "关于重定向网址",
"redirectUrlAboutDescription": "这是用户在验证后将被重定向到的URL。您需要在身份提供者的设置中配置此URL。",
"pangolinAuth": "认证 - Pangolin",
@@ -873,7 +903,7 @@
"inviteAlready": "看起来您已被邀请!",
"inviteAlreadyDescription": "要接受邀请,您必须登录或创建一个帐户。",
"signupQuestion": "已经有一个帐户?",
"login": "登录",
"login": "Log In",
"resourceNotFound": "找不到资源",
"resourceNotFoundDescription": "您要访问的资源不存在。",
"pincodeRequirementsLength": "PIN码必须是6位数字",
@@ -953,13 +983,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 +1147,10 @@
"actionUpdateIdpOrg": "更新 IDP组织",
"actionCreateClient": "创建客户端",
"actionDeleteClient": "删除客户端",
"actionArchiveClient": "Archive Client",
"actionUnarchiveClient": "Unarchive Client",
"actionBlockClient": "Block Client",
"actionUnblockClient": "Unblock Client",
"actionUpdateClient": "更新客户端",
"actionListClients": "列出客户端",
"actionGetClient": "获取客户端",
@@ -1133,14 +1167,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 +1222,7 @@
"sidebarOverview": "概览",
"sidebarHome": "首页",
"sidebarSites": "站点",
"sidebarApprovals": "Approval Requests",
"sidebarResources": "资源",
"sidebarProxyResources": "公开的",
"sidebarClientResources": "非公开的",
@@ -1204,7 +1239,7 @@
"sidebarIdentityProviders": "身份提供商",
"sidebarLicense": "证书",
"sidebarClients": "客户端",
"sidebarUserDevices": "用户",
"sidebarUserDevices": "User Devices",
"sidebarMachineClients": "机",
"sidebarDomains": "域",
"sidebarGeneral": "管理",
@@ -1276,6 +1311,7 @@
"setupErrorCreateAdmin": "创建服务器管理员账户时发生错误。",
"certificateStatus": "证书状态",
"loading": "加载中",
"loadingAnalytics": "Loading Analytics",
"restart": "重启",
"domains": "域",
"domainsDescription": "创建和管理组织中可用的域",
@@ -1303,6 +1339,7 @@
"refreshError": "刷新数据失败",
"verified": "已验证",
"pending": "待定",
"pendingApproval": "Pending Approval",
"sidebarBilling": "计费",
"billing": "计费",
"orgBillingDescription": "管理账单信息和订阅",
@@ -1419,7 +1456,7 @@
"securityKeyRemoveSuccess": "安全密钥删除成功",
"securityKeyRemoveError": "删除安全密钥失败",
"securityKeyLoadError": "加载安全密钥失败",
"securityKeyLogin": "使用安全密钥继续",
"securityKeyLogin": "Use Security Key",
"securityKeyAuthError": "使用安全密钥认证失败",
"securityKeyRecommendation": "考虑在其他设备上注册另一个安全密钥,以确保不会被锁定在您的账户之外。",
"registering": "注册中...",
@@ -1479,7 +1516,7 @@
"IAgreeToThe": "我同意",
"termsOfService": "服务条款",
"and": "和",
"privacyPolicy": "隐私政策"
"privacyPolicy": "隐私政策"
},
"signUpMarketing": {
"keepMeInTheLoop": "通过电子邮件让我在循环中保持新闻、更新和新功能。"
@@ -1546,6 +1583,8 @@
"IntervalSeconds": "正常间隔",
"timeoutSeconds": "超时(秒)",
"timeIsInSeconds": "时间以秒为单位",
"requireDeviceApproval": "Require Device Approvals",
"requireDeviceApprovalDescription": "Users with this role need new devices approved by an admin before they can connect and access resources.",
"retryAttempts": "重试次数",
"expectedResponseCodes": "期望响应代码",
"expectedResponseCodesDescription": "HTTP 状态码表示健康状态。如留空,200-300 被视为健康。",
@@ -1875,7 +1914,7 @@
"orgAuthChooseIdpDescription": "选择您的身份提供商以继续",
"orgAuthNoIdpConfigured": "此机构没有配置任何身份提供者。您可以使用您的 Pangolin 身份登录。",
"orgAuthSignInWithPangolin": "使用 Pangolin 登录",
"orgAuthSignInToOrg": "登录到一个组织",
"orgAuthSignInToOrg": "Sign in to an organization",
"orgAuthSelectOrgTitle": "组织登录",
"orgAuthSelectOrgDescription": "输入您的组织ID以继续",
"orgAuthOrgIdPlaceholder": "您的组织",
@@ -2231,6 +2270,8 @@
"deviceCodeInvalidFormat": "代码必须是9个字符(如A1AJ-N5JD)",
"deviceCodeInvalidOrExpired": "无效或过期的代码",
"deviceCodeVerifyFailed": "验证设备代码失败",
"deviceCodeValidating": "Validating device code...",
"deviceCodeVerifying": "Verifying device authorization...",
"signedInAs": "登录为",
"deviceCodeEnterPrompt": "输入设备上显示的代码",
"continue": "继续",
@@ -2243,7 +2284,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 +2346,7 @@
"identifier": "Identifier",
"deviceLoginUseDifferentAccount": "不是你?使用一个不同的帐户。",
"deviceLoginDeviceRequestingAccessToAccount": "设备正在请求访问此帐户。",
"loginSelectAuthenticationMethod": "Select an authentication method to continue.",
"noData": "无数据",
"machineClients": "机器客户端",
"install": "安装",
@@ -2349,6 +2391,7 @@
"enterConfirmation": "输入确认",
"blueprintViewDetails": "详细信息",
"defaultIdentityProvider": "默认身份提供商",
"defaultIdentityProviderDescription": "当选择默认身份提供商时,用户将自动重定向到提供商进行身份验证。",
"editInternalResourceDialogNetworkSettings": "网络设置",
"editInternalResourceDialogAccessPolicy": "访问策略",
"editInternalResourceDialogAddRoles": "添加角色",
@@ -2392,5 +2435,92 @@
"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",
"deviceInformation": "Device Information",
"deviceInformationDescription": "Information about the device and agent",
"platform": "Platform",
"macosVersion": "macOS Version",
"windowsVersion": "Windows Version",
"iosVersion": "iOS Version",
"androidVersion": "Android Version",
"osVersion": "OS Version",
"kernelVersion": "Kernel Version",
"deviceModel": "Device Model",
"serialNumber": "Serial Number",
"hostname": "Hostname",
"firstSeen": "First Seen",
"lastSeen": "Last Seen",
"deviceSettingsDescription": "View device information and settings",
"devicePendingApprovalDescription": "This device is waiting for approval",
"deviceBlockedDescription": "This device is currently blocked. It won't be able to connect to any resources unless unblocked.",
"unblockClient": "Unblock Client",
"unblockClientDescription": "The device has been unblocked",
"unarchiveClient": "Unarchive Client",
"unarchiveClientDescription": "The device has been unarchived",
"block": "Block",
"unblock": "Unblock",
"deviceActions": "Device Actions",
"deviceActionsDescription": "Manage device status and access",
"devicePendingApprovalBannerDescription": "This device is pending approval. It won't be able to connect to resources until approved.",
"connected": "Connected",
"disconnected": "Disconnected",
"approvalsEmptyStateTitle": "Device Approvals Not Enabled",
"approvalsEmptyStateDescription": "Enable device approvals for roles to require admin approval before users can connect new devices.",
"approvalsEmptyStateStep1Title": "Go to Roles",
"approvalsEmptyStateStep1Description": "Navigate to your organization's roles settings to configure device approvals.",
"approvalsEmptyStateStep2Title": "Enable Device Approvals",
"approvalsEmptyStateStep2Description": "Edit a role and enable the 'Require Device Approvals' option. Users with this role will need admin approval for new devices.",
"approvalsEmptyStatePreviewDescription": "Preview: When enabled, pending device requests will appear here for review",
"approvalsEmptyStateButtonText": "Manage Roles"
}
+10 -10
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",
+25 -12
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>;
+9 -10
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
};
}
+131 -116
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>;
@@ -0,0 +1,3 @@
import { z } from "zod";
export const MaintenanceSchema = z.object({});
+31 -12
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()
+81 -31
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(
+16 -14
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) => {
+4
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
+34 -4
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;
}
+6 -2
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
+33 -32
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
}
}
}
: {})
}
};
@@ -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()
});
+2 -2
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;
+2 -13
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
+24 -26
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) => {
+92 -13
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",
+1 -1
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);
@@ -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
@@ -48,6 +48,11 @@ async function query(orgId: string) {
)
)
.limit(1);
if (!res) {
return null;
}
return {
...res,
orgId: orgLink.orgs.orgId,
+1 -1
View File
@@ -11,4 +11,4 @@
* This file is not licensed under the AGPLv3.
*/
export * from "./getMaintenanceInfo";
export * from "./getMaintenanceInfo";
@@ -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.`
);
}
@@ -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")
+2 -25
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);
+2 -25
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;
+2 -1
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()
+2 -20
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
+34 -33
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",
+1 -1
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;
}
+2 -2
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);
+1 -1
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);
-2
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,
@@ -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
}
});
}
};
+1 -2
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: {
+1 -1
View File
@@ -154,4 +154,4 @@ export async function updateOrg(
createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred")
);
}
}
}
+64 -61
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,
+11 -3
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(
@@ -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, {
+1 -1
View File
@@ -7,4 +7,4 @@ export type GetMaintenanceInfoResponse = {
maintenanceTitle: string | null;
maintenanceMessage: string | null;
maintenanceEstimatedTime: string | null;
}
};
+3 -1
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;
+2 -2
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);
@@ -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(
@@ -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" },
@@ -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(
+2 -2
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(
+5 -3
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");
@@ -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>
@@ -285,7 +285,7 @@ export default function Page() {
<Button
variant="outline"
onClick={() => {
router.push("/admin/idp");
router.push(`/${params.orgId}/settings/idp`);
}}
>
{t("idpSeeAll")}
@@ -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} />
</>
+3 -1
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>
@@ -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
};
}
);
@@ -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>
@@ -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;
@@ -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>
@@ -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
);
}
}
}}
@@ -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
-2
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 {
+29
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 */
}
}
}
+3 -1
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"
-1
View File
@@ -66,4 +66,3 @@ export const ClientDownloadBanner = () => {
};
export default ClientDownloadBanner;
+5 -7
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({
@@ -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 />
+8 -2
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;
@@ -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({
+1 -1
View File
@@ -330,7 +330,7 @@ export default function ExitNodesTable({
isRefreshing={isRefreshing}
columnVisibility={{
type: false,
address: false,
address: false
}}
enableColumnVisibility={true}
/>
+1 -1
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
+4 -3
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 />
+6 -4
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 && (
+16 -5
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}
+1 -4
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;
-1
View File
@@ -39,4 +39,3 @@ export default function OrgInfoCard({}: OrgInfoCardProps) {
</Alert>
);
}
-1
View File
@@ -119,4 +119,3 @@ export default async function OrgLoginPage({
</div>
);
}
+1 -1
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"
@@ -51,4 +51,3 @@ export const PrivateResourcesBanner = ({
};
export default PrivateResourcesBanner;
+6 -3
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"
)}
-1
View File
@@ -20,4 +20,3 @@ export const ProxyResourcesBanner = () => {
};
export default ProxyResourcesBanner;
+1 -1
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")}
+6 -6
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")}
+45 -36
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>
)}
/>
+2 -2
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")
)
});
})
+2 -2
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")
)
});
})
-1
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