Compare commits

..

1726 Commits

Author SHA1 Message Date
Owen Schwartz 2a5b68374f New translations en-us.json (Spanish) 2026-04-20 22:00:58 -07:00
Owen Schwartz 813f27f28f New translations en-us.json (Norwegian Bokmal) 2026-04-20 22:00:56 -07:00
Owen Schwartz 2b372a4e71 New translations en-us.json (Chinese Simplified) 2026-04-20 22:00:55 -07:00
Owen Schwartz 0098c788a2 New translations en-us.json (Turkish) 2026-04-20 22:00:53 -07:00
Owen Schwartz cb4e87fc77 New translations en-us.json (Russian) 2026-04-20 22:00:51 -07:00
Owen Schwartz c8891b20ba New translations en-us.json (Portuguese) 2026-04-20 22:00:49 -07:00
Owen Schwartz ef48f8758a New translations en-us.json (Polish) 2026-04-20 22:00:48 -07:00
Owen Schwartz 27816c0d34 New translations en-us.json (Dutch) 2026-04-20 22:00:46 -07:00
Owen Schwartz a492b6122c New translations en-us.json (Korean) 2026-04-20 22:00:44 -07:00
Owen Schwartz 0985e019a8 New translations en-us.json (Italian) 2026-04-20 22:00:43 -07:00
Owen Schwartz 55c955fa0e New translations en-us.json (German) 2026-04-20 22:00:41 -07:00
Owen Schwartz c37d02a963 New translations en-us.json (Czech) 2026-04-20 22:00:39 -07:00
Owen Schwartz 19124ca613 New translations en-us.json (Bulgarian) 2026-04-20 22:00:37 -07:00
Owen Schwartz 9954e5b2a6 New translations en-us.json (French) 2026-04-20 22:00:35 -07:00
Owen Schwartz c5dc579708 New translations en-us.json (Spanish) 2026-04-20 18:53:04 -07:00
Owen Schwartz 70585816e5 New translations en-us.json (Norwegian Bokmal) 2026-04-20 18:53:02 -07:00
Owen Schwartz 8311b45baa New translations en-us.json (Chinese Simplified) 2026-04-20 18:53:00 -07:00
Owen Schwartz 946ab75ab9 New translations en-us.json (Turkish) 2026-04-20 18:52:59 -07:00
Owen Schwartz 9aef3f9f83 New translations en-us.json (Russian) 2026-04-20 18:52:57 -07:00
Owen Schwartz 10cb32c9f3 New translations en-us.json (Portuguese) 2026-04-20 18:52:55 -07:00
Owen Schwartz eaaa6b68ac New translations en-us.json (Polish) 2026-04-20 18:52:54 -07:00
Owen Schwartz 9762c3f157 New translations en-us.json (Dutch) 2026-04-20 18:52:52 -07:00
Owen Schwartz 70cf676651 New translations en-us.json (Korean) 2026-04-20 18:52:50 -07:00
Owen Schwartz 993e5188cb New translations en-us.json (Italian) 2026-04-20 18:52:49 -07:00
Owen Schwartz 1e9977475c New translations en-us.json (German) 2026-04-20 18:52:47 -07:00
Owen Schwartz 136b26e462 New translations en-us.json (Czech) 2026-04-20 18:52:45 -07:00
Owen Schwartz c855012a94 New translations en-us.json (Bulgarian) 2026-04-20 18:52:43 -07:00
Owen Schwartz ef60e98950 New translations en-us.json (French) 2026-04-20 18:52:42 -07:00
Owen Schwartz 467d809010 New translations en-us.json (Spanish) 2026-04-20 16:58:06 -07:00
Owen Schwartz 63a41c7109 New translations en-us.json (Norwegian Bokmal) 2026-04-20 16:58:05 -07:00
Owen Schwartz 7f140cbbc7 New translations en-us.json (Chinese Simplified) 2026-04-20 16:58:03 -07:00
Owen Schwartz b3cd1028bc New translations en-us.json (Turkish) 2026-04-20 16:58:01 -07:00
Owen Schwartz c79fdc41c6 New translations en-us.json (Russian) 2026-04-20 16:57:59 -07:00
Owen Schwartz 76d8d30aef New translations en-us.json (Portuguese) 2026-04-20 16:57:58 -07:00
Owen Schwartz 7b1f18378e New translations en-us.json (Polish) 2026-04-20 16:57:56 -07:00
Owen Schwartz 15032e34d3 New translations en-us.json (Dutch) 2026-04-20 16:57:54 -07:00
Owen Schwartz 78ede75468 New translations en-us.json (Korean) 2026-04-20 16:57:52 -07:00
Owen Schwartz 78849f3971 New translations en-us.json (Italian) 2026-04-20 16:57:51 -07:00
Owen Schwartz 5e6c3e262e New translations en-us.json (German) 2026-04-20 16:57:49 -07:00
Owen Schwartz ba53a0bdff New translations en-us.json (Czech) 2026-04-20 16:57:47 -07:00
Owen Schwartz fdb3d25a99 New translations en-us.json (Bulgarian) 2026-04-20 16:57:45 -07:00
Owen Schwartz c69aee227e New translations en-us.json (French) 2026-04-20 16:57:44 -07:00
Owen Schwartz 125d1ea2e4 New translations en-us.json (Spanish) 2026-04-20 15:20:54 -07:00
Owen Schwartz eef26ee2c5 New translations en-us.json (Norwegian Bokmal) 2026-04-20 15:20:52 -07:00
Owen Schwartz 6c683f9575 New translations en-us.json (Chinese Simplified) 2026-04-20 15:20:50 -07:00
Owen Schwartz 05184ed7aa New translations en-us.json (Turkish) 2026-04-20 15:20:48 -07:00
Owen Schwartz f477c1c35b New translations en-us.json (Russian) 2026-04-20 15:20:46 -07:00
Owen Schwartz e223bf3683 New translations en-us.json (Portuguese) 2026-04-20 15:20:44 -07:00
Owen Schwartz ace4f85cb7 New translations en-us.json (Polish) 2026-04-20 15:20:43 -07:00
Owen Schwartz aa48d6a111 New translations en-us.json (Dutch) 2026-04-20 15:20:41 -07:00
Owen Schwartz 5d5e8770b9 New translations en-us.json (Korean) 2026-04-20 15:20:39 -07:00
Owen Schwartz e9cc9ee5de New translations en-us.json (Italian) 2026-04-20 15:20:37 -07:00
Owen Schwartz eb7a76bd29 New translations en-us.json (German) 2026-04-20 15:20:35 -07:00
Owen Schwartz 45653b2968 New translations en-us.json (Czech) 2026-04-20 15:20:33 -07:00
Owen Schwartz a06c5f65f3 New translations en-us.json (Bulgarian) 2026-04-20 15:20:31 -07:00
Owen Schwartz e7741d1a3e New translations en-us.json (French) 2026-04-20 15:20:29 -07:00
Owen Schwartz 0ac97ecd5e Merge pull request #2877 from fosrl/dev
Fix import
2026-04-19 11:33:08 -07:00
Owen 387049beac Dynamic -> private import 2026-04-19 11:31:48 -07:00
Owen Schwartz c9240ecb84 Merge pull request #2876 from fosrl/dev
Update translations
2026-04-19 11:27:17 -07:00
Owen Schwartz b87e71c557 Merge pull request #2875 from fosrl/crowdin_dev
New Crowdin updates
2026-04-19 11:26:34 -07:00
Owen Schwartz 866293aa5a New translations en-us.json (Spanish) 2026-04-19 11:25:02 -07:00
Owen Schwartz e142dd32b4 New translations en-us.json (Norwegian Bokmal) 2026-04-19 11:25:00 -07:00
Owen Schwartz 949786dab5 New translations en-us.json (Chinese Simplified) 2026-04-19 11:24:59 -07:00
Owen Schwartz 2dd142b0e9 New translations en-us.json (Turkish) 2026-04-19 11:24:57 -07:00
Owen Schwartz dfd16a6752 New translations en-us.json (Russian) 2026-04-19 11:24:55 -07:00
Owen Schwartz f4454d4d48 New translations en-us.json (Portuguese) 2026-04-19 11:24:54 -07:00
Owen Schwartz e7efc917f0 New translations en-us.json (Polish) 2026-04-19 11:24:52 -07:00
Owen Schwartz 5ffe1ba07d New translations en-us.json (Dutch) 2026-04-19 11:24:51 -07:00
Owen Schwartz b56e2972c4 New translations en-us.json (Korean) 2026-04-19 11:24:49 -07:00
Owen Schwartz ca1a084397 New translations en-us.json (Italian) 2026-04-19 11:24:47 -07:00
Owen Schwartz a7a1f81e9d New translations en-us.json (German) 2026-04-19 11:24:46 -07:00
Owen Schwartz 9c09f17dc5 New translations en-us.json (Czech) 2026-04-19 11:24:44 -07:00
Owen Schwartz 21e2c022c7 New translations en-us.json (Bulgarian) 2026-04-19 11:24:42 -07:00
Owen Schwartz 222cbc886d New translations en-us.json (French) 2026-04-19 11:24:41 -07:00
miloschwartz db2e76bd31 Merge branch 'dev' of https://github.com/fosrl/pangolin into dev 2026-04-19 11:22:46 -07:00
Owen Schwartz bf32cc150d Merge pull request #2874 from fosrl/dev
1.17.1-s.5
2026-04-19 11:21:41 -07:00
miloschwartz 967de0b79f fix metadata for public resources 2026-04-19 11:19:23 -07:00
Owen 22231e6c45 Dont show home 2026-04-19 11:18:43 -07:00
miloschwartz 20ed9966b9 add better page metadata titles 2026-04-18 12:05:54 -07:00
miloschwartz dddf060e1a Merge branch 'cross-org-idp' into dev 2026-04-17 17:27:34 -07:00
miloschwartz a569054e94 dont set org mapping by default 2026-04-17 17:25:21 -07:00
miloschwartz 5885e8eb39 dont allow import idp if not paid 2026-04-17 17:14:21 -07:00
miloschwartz 22964cff0f fix sidebar footer on member page 2026-04-17 14:42:10 -07:00
miloschwartz e952c2d34a fix styles on enterprise edition banners 2026-04-17 14:36:10 -07:00
Owen 0a043af482 Delete update usage on orgs when user deletes
Fixes #2839
2026-04-17 14:28:43 -07:00
miloschwartz 8324445895 add auto provsion section back to create global idp 2026-04-17 10:59:24 -07:00
miloschwartz 796d14a9e4 support org mapping on org idp 2026-04-16 22:12:15 -07:00
miloschwartz 707cc4b275 move idp mode check to a middleware 2026-04-16 21:00:48 -07:00
miloschwartz 93400ace27 import and unassocaite org idp 2026-04-16 20:58:18 -07:00
miloschwartz 6fb8dae966 adjust sidebar 2026-04-16 16:26:13 -07:00
Owen a27a169160 Keep the cert around for a couple of cycles 2026-04-16 15:51:47 -07:00
Owen f0a1de3474 Show picker only when have remote node 2026-04-16 15:33:29 -07:00
Owen Schwartz 79c6fcac95 Merge pull request #2864 from fosrl/dev
1.17.1-s.4
2026-04-16 15:07:29 -07:00
Owen 50697e32c2 Simplify create site 2026-04-16 15:06:30 -07:00
Owen 6fe74a9f8d Allow clearing the instance name on the licenses 2026-04-16 14:44:16 -07:00
Owen a246de2b1f Allow create portal session for enterprise too 2026-04-16 14:44:16 -07:00
miloschwartz 5ac8e4e098 remove redundant pangolin text in footer in saas 2026-04-16 12:05:17 -07:00
Owen aa95e5bb86 Update year 2026-04-15 14:41:13 -07:00
Owen Schwartz 7d13ed79b2 Merge pull request #2862 from fosrl/dev
1.17.3
2026-04-14 20:33:04 -07:00
Owen Schwartz c1f65c802c Merge pull request #2861 from fosrl/crowdin_dev
New Crowdin updates
2026-04-14 20:32:03 -07:00
Owen Schwartz bcc429221e New translations en-us.json (Spanish) 2026-04-14 20:30:58 -07:00
Owen Schwartz bd73609b9e New translations en-us.json (Norwegian Bokmal) 2026-04-14 20:30:56 -07:00
Owen Schwartz 2dbb21a7f2 New translations en-us.json (Chinese Simplified) 2026-04-14 20:30:55 -07:00
Owen Schwartz fe68533ff2 New translations en-us.json (Turkish) 2026-04-14 20:30:53 -07:00
Owen Schwartz 01a40daf38 New translations en-us.json (Russian) 2026-04-14 20:30:51 -07:00
Owen Schwartz 097744275f New translations en-us.json (Portuguese) 2026-04-14 20:30:49 -07:00
Owen Schwartz e481a4d847 New translations en-us.json (Polish) 2026-04-14 20:30:48 -07:00
Owen Schwartz 95c6bb4de6 New translations en-us.json (Dutch) 2026-04-14 20:30:46 -07:00
Owen Schwartz 18e194e152 New translations en-us.json (Korean) 2026-04-14 20:30:44 -07:00
Owen Schwartz b2f391307b New translations en-us.json (Italian) 2026-04-14 20:30:43 -07:00
Owen Schwartz a4da3c7ba2 New translations en-us.json (German) 2026-04-14 20:30:41 -07:00
Owen Schwartz af3abef3bf New translations en-us.json (Czech) 2026-04-14 20:30:39 -07:00
Owen Schwartz f7633a43ce New translations en-us.json (Bulgarian) 2026-04-14 20:30:38 -07:00
Owen Schwartz ffd345f044 New translations en-us.json (French) 2026-04-14 20:30:36 -07:00
Owen ae36d3228f Remove journal 2026-04-14 20:23:56 -07:00
Owen 1c78a6b483 Adjust self serve 2026-04-14 20:21:34 -07:00
Owen Schwartz b6c6590aad New translations en-us.json (Norwegian Bokmal) 2026-04-14 19:48:12 -07:00
Owen Schwartz 5a792e9913 New translations en-us.json (Chinese Simplified) 2026-04-14 19:48:11 -07:00
Owen Schwartz a2f822889d New translations en-us.json (Turkish) 2026-04-14 19:48:09 -07:00
Owen Schwartz 83ba463a34 New translations en-us.json (Russian) 2026-04-14 19:48:07 -07:00
Owen Schwartz a909c5cbe0 New translations en-us.json (Portuguese) 2026-04-14 19:48:06 -07:00
Owen Schwartz d615f34f94 New translations en-us.json (Polish) 2026-04-14 19:48:04 -07:00
Owen Schwartz 37378895cf New translations en-us.json (Dutch) 2026-04-14 19:48:02 -07:00
Owen Schwartz 19ef055296 New translations en-us.json (Korean) 2026-04-14 19:48:00 -07:00
Owen Schwartz 599fa5eb30 New translations en-us.json (Italian) 2026-04-14 19:47:59 -07:00
Owen Schwartz 4d82b37cab New translations en-us.json (German) 2026-04-14 19:47:57 -07:00
Owen Schwartz 77d01d50db New translations en-us.json (Czech) 2026-04-14 19:47:55 -07:00
Owen Schwartz 013c1ab92c New translations en-us.json (Bulgarian) 2026-04-14 19:47:53 -07:00
Owen Schwartz d4fc60f2f4 New translations en-us.json (Spanish) 2026-04-14 19:47:52 -07:00
Owen Schwartz cd25cde47f New translations en-us.json (French) 2026-04-14 19:47:50 -07:00
Owen af709331fb Add missing DnsRecords type 2026-04-14 19:46:25 -07:00
Owen e20a21bacd Contact support 2026-04-14 19:46:19 -07:00
Owen 74b3b283f7 Fix #2848 2026-04-13 21:30:19 -07:00
Owen Schwartz 9fe4f78269 Merge pull request #2857 from fosrl/dev
Proxy targets returns an array
2026-04-13 20:57:15 -07:00
Owen 03d95874e6 Proxy targets returns an array 2026-04-13 20:44:35 -07:00
Milo Schwartz bd3d6994c1 Merge pull request #2856 from fosrl/update-readme
fix image
2026-04-13 20:29:36 -07:00
miloschwartz 5fd78817a8 fix image 2026-04-13 20:28:05 -07:00
Owen Schwartz 72bc125f84 Merge pull request #2854 from fosrl/dev
Rename script
2026-04-13 16:23:39 -07:00
Owen 5d51af4330 Rename script 2026-04-13 16:22:53 -07:00
Owen Schwartz b18ea66def Merge pull request #2853 from fosrl/dev
1.17.1-s.1
2026-04-13 12:28:08 -07:00
Owen 93998f9fd5 Fix ts issue 2026-04-13 12:27:29 -07:00
Owen c554e69514 Fill the width 2026-04-13 12:11:15 -07:00
Owen a6e10e55cc Handle grandfather on the front end 2026-04-13 12:08:30 -07:00
Owen Schwartz 41f541a531 Merge pull request #2852 from fosrl/dev
1.17.1-s.0
2026-04-13 11:36:36 -07:00
Owen 9cb1043545 Push back date 2026-04-13 11:33:51 -07:00
Owen Schwartz 96e33d33b0 Merge pull request #2851 from fosrl/crowdin_dev
New Crowdin updates
2026-04-13 11:25:53 -07:00
Owen Schwartz ccc7003ac1 New translations en-us.json (Spanish) 2026-04-13 11:24:32 -07:00
Owen Schwartz 93cbd47b5d New translations en-us.json (Norwegian Bokmal) 2026-04-13 11:24:30 -07:00
Owen Schwartz 8b808e44b6 New translations en-us.json (Chinese Simplified) 2026-04-13 11:24:28 -07:00
Owen Schwartz 0644e26297 New translations en-us.json (Turkish) 2026-04-13 11:24:27 -07:00
Owen Schwartz 682653b977 New translations en-us.json (Russian) 2026-04-13 11:24:25 -07:00
Owen Schwartz 0053cfc8fc New translations en-us.json (Portuguese) 2026-04-13 11:24:23 -07:00
Owen Schwartz 5cb62a30cc New translations en-us.json (Polish) 2026-04-13 11:24:21 -07:00
Owen Schwartz e596a63058 New translations en-us.json (Dutch) 2026-04-13 11:24:19 -07:00
Owen Schwartz 3ec32afb37 New translations en-us.json (Korean) 2026-04-13 11:24:17 -07:00
Owen Schwartz 0189a86757 New translations en-us.json (Italian) 2026-04-13 11:24:15 -07:00
Owen Schwartz ee32307654 New translations en-us.json (German) 2026-04-13 11:24:14 -07:00
Owen Schwartz 2f08e6b838 New translations en-us.json (Czech) 2026-04-13 11:24:12 -07:00
Owen Schwartz c8a3fc350d New translations en-us.json (Bulgarian) 2026-04-13 11:24:10 -07:00
Owen Schwartz dc63ef1284 New translations en-us.json (French) 2026-04-13 11:24:07 -07:00
Owen 92332fb02f Hide the home unless you have it 2026-04-13 11:17:14 -07:00
miloschwartz acc6a26654 update readme 2026-04-13 11:12:09 -07:00
Owen 2bd4d2faaf Merge branch 'main' into dev 2026-04-13 10:50:12 -07:00
Owen 1e77ead488 Adjust functioning of ee button 2026-04-13 10:49:57 -07:00
Milo Schwartz c008ef7c1b Merge pull request #2850 from fosrl/miloschwartz-patch-1
Update README.md
2026-04-13 10:04:55 -07:00
Milo Schwartz 02dfeed3ce Update README.md 2026-04-13 13:03:53 -04:00
Owen Schwartz 34cc2e0ed1 Merge pull request #2841 from AdnanSilajdzic/fix/worldmap-typescript-followup
fix(worldmap): correct topojson feature typing
2026-04-13 09:42:05 -07:00
miloschwartz f5d0694574 change user devices column name from online to connected 2026-04-12 15:27:14 -07:00
miloschwartz f91da2ec46 fix no default idp selector showing on ce closes #2813 2026-04-12 15:20:09 -07:00
miloschwartz 89471a0174 include site name in target dropdown in public resources table 2026-04-12 15:09:40 -07:00
Adnan Silajdzic 0cb04d0290 fix(worldmap): correct topojson feature typing 2026-04-12 17:05:53 +00:00
miloschwartz e118e5b047 add list alises endpoint 2026-04-11 21:03:35 -07:00
miloschwartz 7e4e8ea266 add niceId to list user resources 2026-04-11 17:56:16 -07:00
Owen 2f386f8e47 Grandfather in old users 2026-04-11 16:59:43 -07:00
Owen f4ea572f6b Fix #2828 2026-04-11 16:50:28 -07:00
Owen Schwartz 825df7da63 Merge pull request #2806 from jbelke/fix-invite-email-encoding
Fix invite email encoding
2026-04-11 16:37:49 -07:00
Owen Schwartz cd34f0a7b0 Merge pull request #2799 from LaurenceJJones/fix/proxy-target-deletion
fix: use targetId as row identifier
2026-04-11 16:35:09 -07:00
Owen Schwartz b1b22c439a Merge pull request #2825 from AdnanSilajdzic/fix/worldmap-hover-stuck-public
fix(analytics): prevent countries from getting stuck highlighted on world map
2026-04-11 16:32:32 -07:00
Owen eac747849b Restrict namespaces to paid plans due to abuse 2026-04-11 14:22:00 -07:00
Adnan Silajdzic 1aedf9da0a fix(worldmap): avoid stuck country hover state 2026-04-10 14:37:48 +00:00
miloschwartz 840684aeba dont show wildcard in domain picker 2026-04-09 17:54:25 -04:00
miloschwartz f57012eb90 dont show international domain warning when capital letter present 2026-04-09 17:06:04 -04:00
miloschwartz 34387d9859 simplify wildcard domain on non pangolin-dns 2026-04-09 17:04:28 -04:00
miloschwartz 80f5914fdd add pluto 2026-04-09 16:15:19 -04:00
miloschwartz eaa70da4dd add pluto 2026-04-09 16:14:46 -04:00
Owen 466f137590 Fix migration by testing for orphans 2026-04-09 10:29:51 -04:00
Joshua Belke 028df8bf27 fix: remove encodeURIComponent from invite link email parameter
The @ symbol in email addresses was being encoded as %40 when
constructing invite URLs, causing broken or garbled links when
copied/shared by users.

- Remove encodeURIComponent(email) from server-side invite link
  construction in inviteUser.ts (both new invite and regenerate paths)
- Remove encodeURIComponent(email) from client-side redirect URLs in
  InviteStatusCard.tsx (login, signup, and useEffect redirect paths)
- Valid Zod-validated email addresses do not contain characters that
  require URL encoding for safe query parameter use (@ is permitted
  in query strings per RFC 3986 §3.4)
2026-04-07 14:58:27 -04:00
Owen 28ef5238c9 Add CODEOWNERS 2026-04-07 11:36:02 -04:00
Laurence 7d3d5b2b22 use targetid also on proxy create as that also has same issue 2026-04-06 14:17:04 +01:00
Laurence 81eba50c9a fix: use targetId as row identifier
fix: 2797
2026-04-06 14:03:33 +01:00
Owen Schwartz 3436105bec Merge pull request #2784 from fosrl/dev
Try to prevent deadlocks
2026-04-03 23:01:09 -04:00
Owen d948d2ec33 Try to prevent deadlocks 2026-04-03 22:55:04 -04:00
Owen Schwartz 4b3375ab8e Merge pull request #2783 from fosrl/dev
Fix 1.17.0
2026-04-03 22:42:03 -04:00
Owen 6b8a3c8d77 Revert #2570
Fix #2782
2026-04-03 22:37:42 -04:00
Owen ba9794c067 Put middleware back
Fix #2781
2026-04-03 22:16:26 -04:00
Owen Schwartz 6ce165bfd5 Merge pull request #2780 from fosrl/dev
1.17.0
2026-04-03 18:19:40 -04:00
Owen eb4b2daaab Use the right encryption 2026-04-03 17:59:21 -04:00
Owen 8cbc8dec89 Generate address 2026-04-03 17:25:39 -04:00
Owen e89e60d50b Encrypt the streaming data 2026-04-03 15:33:29 -04:00
Owen c45308f234 Send to the right place 2026-04-03 15:33:29 -04:00
Owen Schwartz 40205c40c5 Merge pull request #2779 from fosrl/crowdin_dev
New Crowdin updates
2026-04-03 15:00:11 -04:00
Owen Schwartz f3fe2dd33b New translations en-us.json (Spanish) 2026-04-03 14:58:56 -04:00
Owen Schwartz 8edcc45033 New translations en-us.json (Norwegian Bokmal) 2026-04-03 14:58:55 -04:00
Owen Schwartz 91471a4aca New translations en-us.json (Chinese Simplified) 2026-04-03 14:58:53 -04:00
Owen Schwartz ae2c37a2f6 New translations en-us.json (Turkish) 2026-04-03 14:58:52 -04:00
Owen Schwartz c8208f0a88 New translations en-us.json (Russian) 2026-04-03 14:58:50 -04:00
Owen Schwartz e11dfbd29c New translations en-us.json (Portuguese) 2026-04-03 14:58:49 -04:00
Owen Schwartz b375d20598 New translations en-us.json (Polish) 2026-04-03 14:58:48 -04:00
Owen Schwartz c4b82c69f8 New translations en-us.json (Dutch) 2026-04-03 14:58:47 -04:00
Owen Schwartz c9a00420a0 New translations en-us.json (Korean) 2026-04-03 14:58:45 -04:00
Owen Schwartz 36ef9cd442 New translations en-us.json (Italian) 2026-04-03 14:58:44 -04:00
Owen Schwartz 5e08779ab0 New translations en-us.json (German) 2026-04-03 14:58:42 -04:00
Owen Schwartz 16a0e1ce7b New translations en-us.json (Czech) 2026-04-03 14:58:41 -04:00
Owen Schwartz 8b03484ade New translations en-us.json (Bulgarian) 2026-04-03 14:58:39 -04:00
Owen Schwartz 9da9974adf New translations en-us.json (French) 2026-04-03 14:58:38 -04:00
Owen Schwartz 6f80cf3db2 New translations en-us.json (Spanish) 2026-04-03 13:03:44 -04:00
Owen Schwartz 76d8f44779 New translations en-us.json (Norwegian Bokmal) 2026-04-03 13:03:43 -04:00
Owen Schwartz 700c92efcb New translations en-us.json (Chinese Simplified) 2026-04-03 13:03:41 -04:00
Owen Schwartz d17e0c9f50 New translations en-us.json (Turkish) 2026-04-03 13:03:39 -04:00
Owen Schwartz f00b9794f5 New translations en-us.json (Russian) 2026-04-03 13:03:38 -04:00
Owen Schwartz daff59c93f New translations en-us.json (Portuguese) 2026-04-03 13:03:36 -04:00
Owen Schwartz aa8954366c New translations en-us.json (Polish) 2026-04-03 13:03:35 -04:00
Owen Schwartz 87464d53bd New translations en-us.json (Dutch) 2026-04-03 13:03:33 -04:00
Owen Schwartz e04f17c9aa New translations en-us.json (Korean) 2026-04-03 13:03:32 -04:00
Owen Schwartz b25e3499d8 New translations en-us.json (Italian) 2026-04-03 13:03:30 -04:00
Owen Schwartz 2e6f74a6f8 New translations en-us.json (German) 2026-04-03 13:03:28 -04:00
Owen Schwartz 8eee0ca5a5 New translations en-us.json (Czech) 2026-04-03 13:03:26 -04:00
Owen Schwartz c2ebc0a0ff New translations en-us.json (Bulgarian) 2026-04-03 13:03:24 -04:00
Owen Schwartz 03c905a7af New translations en-us.json (French) 2026-04-03 13:03:22 -04:00
Owen Schwartz 035644eaf7 Merge pull request #2778 from fosrl/dev
1.17.0-s.2
2026-04-03 12:35:03 -04:00
Owen 8ce45a1acd Update casting again 2026-04-03 12:34:37 -04:00
Owen Schwartz 16e7233a3e Merge pull request #2777 from fosrl/dev
1.17.0-s.1
2026-04-03 12:19:23 -04:00
Owen a331dd3fb4 Merge branch 'dev' of github.com:fosrl/pangolin into dev 2026-04-03 12:18:33 -04:00
Owen Schwartz e3e2938b28 Merge pull request #2761 from fosrl/crowdin_dev
New Crowdin updates
2026-04-03 12:16:31 -04:00
Owen 73e96b1b28 Type cast data 2026-04-03 12:15:39 -04:00
miloschwartz b8194295ec fix translation syntax err 2026-04-03 11:47:54 -04:00
miloschwartz 382a46dfff fix text formatting in delete dialog 2026-04-03 11:44:56 -04:00
Owen Schwartz 1f74e1b320 Merge pull request #2776 from fosrl/dev
1.17.0-s.0
2026-04-03 11:39:35 -04:00
Owen fee780cb81 Add siem to migration 2026-04-03 11:32:02 -04:00
Owen 5056cba85d Add siem to migration 2026-04-03 11:32:02 -04:00
miloschwartz dab38ff82c use debug log on log stream start 2026-04-03 11:16:56 -04:00
Owen d83fa63af5 Fix to null out the rewrite on the frontend too 2026-04-02 21:58:14 -04:00
Owen d5837ab718 Fix to use the stored data 2026-04-02 21:57:59 -04:00
Owen Schwartz f85cfc4c68 New translations en-us.json (Spanish) 2026-04-02 18:35:13 -04:00
Owen Schwartz 0b2aceafe0 New translations en-us.json (Norwegian Bokmal) 2026-04-02 18:35:11 -04:00
Owen Schwartz 059db34a53 New translations en-us.json (Chinese Simplified) 2026-04-02 18:35:10 -04:00
Owen Schwartz bc1ea86b4e New translations en-us.json (Turkish) 2026-04-02 18:35:09 -04:00
Owen Schwartz 9f2ced1933 New translations en-us.json (Russian) 2026-04-02 18:35:08 -04:00
Owen Schwartz 013cff9b6e New translations en-us.json (Portuguese) 2026-04-02 18:35:06 -04:00
Owen Schwartz aa19437031 New translations en-us.json (Polish) 2026-04-02 18:35:05 -04:00
Owen Schwartz e848ef848b New translations en-us.json (Dutch) 2026-04-02 18:35:03 -04:00
Owen Schwartz bb6605337f New translations en-us.json (Korean) 2026-04-02 18:35:02 -04:00
Owen Schwartz 8df8383468 New translations en-us.json (Italian) 2026-04-02 18:35:01 -04:00
Owen Schwartz a7e9de3ac4 New translations en-us.json (German) 2026-04-02 18:34:59 -04:00
Owen Schwartz 8df41f514e New translations en-us.json (Czech) 2026-04-02 18:34:58 -04:00
Owen Schwartz c2bf50b121 New translations en-us.json (Bulgarian) 2026-04-02 18:34:56 -04:00
Owen Schwartz 4e7dcbd7b5 New translations en-us.json (French) 2026-04-02 18:34:55 -04:00
Owen b7ccb92236 Merge branch 'main' into dev 2026-04-02 17:39:25 -04:00
Owen Schwartz 23a151dd45 Merge pull request #2771 from LaurenceJJones/feature/systemd-install-instructions
enhance: Systemd newt instructions
2026-04-02 12:13:44 -04:00
Laurence 122079ddb2 split unix to linux and macos, show method everything other than windows, change nixos all to flake so makes sense under method 2026-04-02 17:05:50 +01:00
Owen Schwartz 1d0b0ae6ec Merge pull request #2770 from fosrl/revert-2766-feature/systemd-install-instructions
Revert "enhance: Systemd newt unit instructions"
2026-04-02 11:43:15 -04:00
Owen Schwartz a55dd769cf Revert "enhance: Systemd newt unit instructions" 2026-04-02 11:43:01 -04:00
Owen Schwartz f1a0bc97e3 New translations en-us.json (Spanish) 2026-04-02 11:42:52 -04:00
Owen Schwartz a57dfd1d12 New translations en-us.json (Norwegian Bokmal) 2026-04-02 11:42:50 -04:00
Owen Schwartz c0a8304b91 New translations en-us.json (Chinese Simplified) 2026-04-02 11:42:48 -04:00
Owen Schwartz ab7b968e28 New translations en-us.json (Turkish) 2026-04-02 11:42:47 -04:00
Owen Schwartz f10b40c3b0 New translations en-us.json (Russian) 2026-04-02 11:42:45 -04:00
Owen Schwartz 7878ac9c76 New translations en-us.json (Portuguese) 2026-04-02 11:42:43 -04:00
Owen Schwartz 0752951842 New translations en-us.json (Polish) 2026-04-02 11:42:41 -04:00
Owen Schwartz 06bb6636a1 New translations en-us.json (Dutch) 2026-04-02 11:42:39 -04:00
Owen Schwartz 2fdd332a31 New translations en-us.json (Korean) 2026-04-02 11:42:38 -04:00
Owen Schwartz 98b1e9546a New translations en-us.json (Italian) 2026-04-02 11:42:36 -04:00
Owen Schwartz 184aa65c6d New translations en-us.json (German) 2026-04-02 11:42:34 -04:00
Owen Schwartz 70b3a432a4 New translations en-us.json (Czech) 2026-04-02 11:42:33 -04:00
Owen Schwartz fb4fc75bd8 New translations en-us.json (Bulgarian) 2026-04-02 11:42:31 -04:00
Owen Schwartz 0479ed9e7f New translations en-us.json (French) 2026-04-02 11:42:29 -04:00
Owen Schwartz 1dc3409135 Merge pull request #2766 from LaurenceJJones/feature/systemd-install-instructions
enhance: Systemd newt unit instructions
2026-04-02 11:40:50 -04:00
Laurence 1bb89fce26 enhance: Systemd newt unit
Add a systemd unit option directly from dashboard to prevent copy and paste mistakes
2026-04-02 12:21:53 +01:00
Owen Schwartz 8f3fbb94d2 New translations en-us.json (Spanish) 2026-04-01 09:58:52 -07:00
Owen Schwartz e8c35bec1c New translations en-us.json (Norwegian Bokmal) 2026-04-01 09:58:50 -07:00
Owen Schwartz 728e7252eb New translations en-us.json (Chinese Simplified) 2026-04-01 09:58:49 -07:00
Owen Schwartz 1218507f7d New translations en-us.json (Turkish) 2026-04-01 09:58:47 -07:00
Owen Schwartz a2dff0a35d New translations en-us.json (Russian) 2026-04-01 09:58:46 -07:00
Owen Schwartz f411180908 New translations en-us.json (Portuguese) 2026-04-01 09:58:44 -07:00
Owen Schwartz 231a19b679 New translations en-us.json (Polish) 2026-04-01 09:58:43 -07:00
Owen Schwartz 58a87a986a New translations en-us.json (Dutch) 2026-04-01 09:58:41 -07:00
Owen Schwartz 61a78ef352 New translations en-us.json (Korean) 2026-04-01 09:58:39 -07:00
Owen Schwartz e28e5ebb4e New translations en-us.json (Italian) 2026-04-01 09:58:38 -07:00
Owen Schwartz 19cef8c453 New translations en-us.json (German) 2026-04-01 09:58:36 -07:00
Owen Schwartz 1290d6cd5c New translations en-us.json (Czech) 2026-04-01 09:58:35 -07:00
Owen Schwartz ad301074db New translations en-us.json (Bulgarian) 2026-04-01 09:58:33 -07:00
Owen Schwartz 30a756d254 New translations en-us.json (French) 2026-04-01 09:58:32 -07:00
Owen 363c13c387 Impvove communication 2026-04-01 09:53:49 -07:00
Owen 08e4afaef0 Update hp log message 2026-03-31 17:06:56 -07:00
Owen 69aa6e2d1d Prevent increase in writes on reconnect 2026-03-31 17:00:06 -07:00
Owen 547865e0da Mark targets unhealthy when site is down
Fix #2675
Fix #2700
Fix #1742
2026-03-31 16:24:53 -07:00
Owen 3a9e79e6d5 Filter only newt sites on private resources 2026-03-31 16:17:17 -07:00
Owen Schwartz 0fc1aa9191 Merge pull request #2755 from fosrl/dev
Update go version
2026-03-31 16:04:11 -07:00
Owen f34a3559be Update go version 2026-03-31 16:03:22 -07:00
Owen Schwartz ddf417f4ca Merge pull request #2753 from fosrl/dev
Update security
2026-03-31 15:27:47 -07:00
Owen f2abbf01e5 Update security 2026-03-31 15:26:39 -07:00
Owen Schwartz d08be59055 Merge pull request #2752 from fosrl/dev
1.17.0-rc.0
2026-03-31 15:24:25 -07:00
Owen Schwartz 44bb87e4ac Merge pull request #2751 from fosrl/crowdin_dev
New Crowdin updates
2026-03-31 15:21:34 -07:00
Owen Schwartz 1d2f1405aa New translations en-us.json (Norwegian Bokmal) 2026-03-31 15:20:05 -07:00
Owen Schwartz ff64a79014 New translations en-us.json (Chinese Simplified) 2026-03-31 15:20:04 -07:00
Owen Schwartz f6cdadbc2d New translations en-us.json (Turkish) 2026-03-31 15:20:02 -07:00
Owen Schwartz 546769ca66 New translations en-us.json (Spanish) 2026-03-31 15:20:00 -07:00
Owen Schwartz d07996d435 New translations en-us.json (Russian) 2026-03-31 15:19:59 -07:00
Owen Schwartz 467808f174 New translations en-us.json (Portuguese) 2026-03-31 15:19:57 -07:00
Owen Schwartz 64d3c6b2d9 New translations en-us.json (Polish) 2026-03-31 15:19:56 -07:00
Owen Schwartz 75193bb0a2 New translations en-us.json (Dutch) 2026-03-31 15:19:54 -07:00
Owen Schwartz 82ba2bd809 New translations en-us.json (Korean) 2026-03-31 15:19:52 -07:00
Owen Schwartz 8559942c5c New translations en-us.json (Italian) 2026-03-31 15:19:51 -07:00
Owen Schwartz a7fefc84a8 New translations en-us.json (German) 2026-03-31 15:19:49 -07:00
Owen Schwartz c8e83fedeb New translations en-us.json (Czech) 2026-03-31 15:19:48 -07:00
Owen Schwartz 4bf148a4bf New translations en-us.json (Bulgarian) 2026-03-31 15:19:46 -07:00
Owen Schwartz 44664faf3c New translations en-us.json (French) 2026-03-31 15:19:45 -07:00
Owen Schwartz 322c136d1f Merge pull request #2748 from jaydeep-pipaliya/fix/empty-targets-toast-message
fix: show contextual toast when saving with no targets
2026-03-31 15:11:12 -07:00
Owen 3b8dd45a73 Translate siem 2026-03-31 15:09:14 -07:00
Owen c1bd36231d Default to approved 2026-03-31 14:46:23 -07:00
Owen 2cee723f0e Handle online and offline for wireguard sites 2026-03-31 14:41:02 -07:00
Owen edfeec900d Define db type 2026-03-31 14:25:47 -07:00
Owen 958bde2090 Merge branch 'siem' into dev 2026-03-31 14:20:46 -07:00
Owen 29b272f5d5 Restrict saas 2026-03-31 14:08:50 -07:00
Owen 9162ac6d91 Add missing headers 2026-03-31 14:07:28 -07:00
Owen fe30bb280e Add option for how to batch 2026-03-31 14:04:58 -07:00
Owen a1e9396999 Handle backlog better 2026-03-31 13:47:32 -07:00
miloschwartz 2a1c290dff dont show identifier on create private resource 2026-03-31 12:32:03 -07:00
Owen d155d7e31b Update formatting 2026-03-31 12:26:31 -07:00
Owen 3dc258da16 Log streaming manager pass 1 2026-03-31 12:22:37 -07:00
Owen 0db1397f2f Add log connection audit dedicated file 2026-03-31 12:02:02 -07:00
Owen 0254fb1695 Small ui tweaks 2026-03-31 11:56:19 -07:00
Owen 954b492aa9 Fix imports 2026-03-31 11:52:08 -07:00
Owen 8aadc10530 Merge branch 'main' into dev 2026-03-31 11:44:31 -07:00
Owen 6ea719c50f Pin 2026-03-31 11:44:18 -07:00
Owen b50886179a Implement fixes to ui 2026-03-31 11:43:52 -07:00
jaydeep-pipaliya e06f2f47b1 fix: show contextual toast when saving with no targets
Instead of always showing "Settings updated" when saving, show
"Targets cleared" when the target list is empty. This gives the user
accurate feedback without blocking the save action.

Fixes #586
2026-03-31 11:48:56 +05:30
Owen ed8c8bedcd Add picker 2026-03-30 21:46:11 -07:00
Owen 1711e39219 Add logos 2026-03-30 21:42:41 -07:00
Owen a73879ec7a Fix formatting 2026-03-30 21:35:37 -07:00
Owen 45c613dec4 Tweaking the ui 2026-03-30 21:09:14 -07:00
Owen 5150a2c386 Basic ui done 2026-03-30 21:00:05 -07:00
Owen ca0dd09964 Add crud 2026-03-30 20:35:00 -07:00
Owen 5e0e4f1452 Update book a demo 2026-03-30 20:16:35 -07:00
Owen 3ed72dd96b Add missing colums to migrations 2026-03-30 18:16:22 -07:00
Owen b8d7d5c910 Add name to provisioning 2026-03-30 17:18:38 -07:00
Owen 673b8b7af5 Add userInviteRoles migration 2026-03-30 17:03:12 -07:00
Owen a651e50759 Fix merge from main 2026-03-30 16:59:05 -07:00
Owen 6484e8e302 Make work for demo 2026-03-30 16:57:36 -07:00
Owen b01d266629 Migration 1.17 first pass 2026-03-30 16:55:37 -07:00
Owen 4465b05404 Merge branch 'provisioning-room' into dev 2026-03-30 16:19:11 -07:00
Owen d1182c3a59 Merge branch 'main' into dev 2026-03-30 15:53:46 -07:00
Owen cb6c47678b Add regions to blueprints 2026-03-30 14:43:49 -07:00
Owen Schwartz 8106620a19 Merge pull request #2149 from infiniteWays/feature/region-rules
Region-based Resource Rules
2026-03-30 14:37:17 -07:00
Owen be3e066843 Merge branch 'dev' into feature/region-rules 2026-03-30 14:36:50 -07:00
Owen Schwartz e345c6ee6e Merge pull request #2627 from shreyaspapi/fix/1547-persist-user-locale
fix: persist user locale preference to database (#1547)
2026-03-30 14:29:15 -07:00
Owen 073b89b355 make path the default 2026-03-30 14:18:10 -07:00
Owen Schwartz 5cad07f8ad Merge pull request #2623 from rodneyosodo/fix/errcheck
refactor(install): improve resource cleanup and remove unused funcs
2026-03-30 14:12:41 -07:00
Owen Schwartz f9d872558e Merge pull request #2624 from fosrl/dependabot/go_modules/install/github.com/charmbracelet/huh-1.0.0
Bump github.com/charmbracelet/huh from 0.8.0 to 1.0.0 in /install
2026-03-30 14:08:26 -07:00
Owen Schwartz c5015d02ae Merge pull request #2646 from LaurenceJJones/feature/installer-default-dir
feat(installer): add default install directory with existing install …
2026-03-30 14:08:00 -07:00
Owen Schwartz 48013228c1 Merge pull request #2653 from shleeable/patch-1
feat(installer): Update docker-compose.yml with HTTPS/3 + QUIC support via traefik
2026-03-30 14:07:34 -07:00
Owen dbafffe73d Update crowdsec and add comment 2026-03-30 14:06:56 -07:00
Owen Schwartz 61cbcb2a06 Merge pull request #2741 from fosrl/dependabot/npm_and_yarn/multi-0b8106bf31
Bump fast-xml-parser and @aws-sdk/xml-builder
2026-03-30 13:58:19 -07:00
Owen Schwartz 89c1ad5d98 Merge pull request #2738 from fosrl/dependabot/github_actions/sigstore/cosign-installer-4.1.1
Bump sigstore/cosign-installer from 4.1.0 to 4.1.1
2026-03-30 13:55:27 -07:00
Owen Schwartz b343ca6290 Merge pull request #2687 from fosrl/dependabot/npm_and_yarn/next-15.5.14
Bump next from 15.5.12 to 15.5.14
2026-03-30 13:55:19 -07:00
dependabot[bot] b913466671 Bump next from 15.5.12 to 15.5.14
Bumps [next](https://github.com/vercel/next.js) from 15.5.12 to 15.5.14.
- [Release notes](https://github.com/vercel/next.js/releases)
- [Changelog](https://github.com/vercel/next.js/blob/canary/release.js)
- [Commits](https://github.com/vercel/next.js/compare/v15.5.12...v15.5.14)

---
updated-dependencies:
- dependency-name: next
  dependency-version: 15.5.14
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-30 20:37:38 +00:00
Owen Schwartz 9054f4f9c3 Merge pull request #2683 from fosrl/dependabot/npm_and_yarn/flatted-3.4.2
Bump flatted from 3.3.3 to 3.4.2
2026-03-30 13:37:18 -07:00
Owen Schwartz 3915024d9a Merge pull request #2714 from fosrl/dependabot/npm_and_yarn/dev-patch-updates-3753551584
Bump the dev-patch-updates group across 1 directory with 3 updates
2026-03-30 13:36:49 -07:00
dependabot[bot] 7d1085b43f Bump fast-xml-parser and @aws-sdk/xml-builder
Bumps [fast-xml-parser](https://github.com/NaturalIntelligence/fast-xml-parser) and [@aws-sdk/xml-builder](https://github.com/aws/aws-sdk-js-v3/tree/HEAD/packages-internal/xml-builder). These dependencies needed to be updated together.

Updates `fast-xml-parser` from 5.5.6 to 5.5.8
- [Release notes](https://github.com/NaturalIntelligence/fast-xml-parser/releases)
- [Changelog](https://github.com/NaturalIntelligence/fast-xml-parser/blob/master/CHANGELOG.md)
- [Commits](https://github.com/NaturalIntelligence/fast-xml-parser/compare/v5.5.6...v5.5.8)

Updates `@aws-sdk/xml-builder` from 3.972.12 to 3.972.16
- [Release notes](https://github.com/aws/aws-sdk-js-v3/releases)
- [Changelog](https://github.com/aws/aws-sdk-js-v3/blob/main/packages-internal/xml-builder/CHANGELOG.md)
- [Commits](https://github.com/aws/aws-sdk-js-v3/commits/HEAD/packages-internal/xml-builder)

---
updated-dependencies:
- dependency-name: fast-xml-parser
  dependency-version: 5.5.8
  dependency-type: indirect
- dependency-name: "@aws-sdk/xml-builder"
  dependency-version: 3.972.16
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-30 20:36:35 +00:00
Owen Schwartz 7c2477cccc Merge pull request #2717 from fosrl/dependabot/npm_and_yarn/multi-bf05dc1ecf
Bump picomatch
2026-03-30 13:36:21 -07:00
Owen Schwartz 5aecb5fb90 Merge pull request #2727 from fosrl/dependabot/npm_and_yarn/yaml-2.8.3
Bump yaml from 2.8.2 to 2.8.3
2026-03-30 13:36:06 -07:00
Owen Schwartz f86d040ee4 Merge pull request #2728 from fosrl/dependabot/npm_and_yarn/nodemailer-8.0.4
Bump nodemailer from 8.0.1 to 8.0.4
2026-03-30 13:35:48 -07:00
Owen Schwartz ed32717b3f Merge pull request #2730 from fosrl/dependabot/npm_and_yarn/multi-95b84c9cdf
Bump brace-expansion
2026-03-30 13:35:31 -07:00
Owen Schwartz aab8462134 Merge pull request #2733 from fosrl/dependabot/npm_and_yarn/path-to-regexp-8.4.0
Bump path-to-regexp from 8.3.0 to 8.4.0
2026-03-30 13:34:56 -07:00
Owen 04943fb4a6 Fix access log 2026-03-30 11:58:48 -07:00
Owen e0c96e7224 Configure connection log retention time 2026-03-30 11:31:46 -07:00
Owen caacd1e677 Remove rewrite if match is removed 2026-03-30 11:11:18 -07:00
Owen c995c5a674 Dont batch set on sqlite 2026-03-30 11:02:09 -07:00
Owen 1e9544af07 Customize table a little more 2026-03-29 20:29:31 -07:00
dependabot[bot] c20dfdabfb Bump the dev-patch-updates group across 1 directory with 3 updates
Bumps the dev-patch-updates group with 3 updates in the / directory: [@tailwindcss/postcss](https://github.com/tailwindlabs/tailwindcss/tree/HEAD/packages/@tailwindcss-postcss), [esbuild](https://github.com/evanw/esbuild) and [tailwindcss](https://github.com/tailwindlabs/tailwindcss/tree/HEAD/packages/tailwindcss).


Updates `@tailwindcss/postcss` from 4.2.1 to 4.2.2
- [Release notes](https://github.com/tailwindlabs/tailwindcss/releases)
- [Changelog](https://github.com/tailwindlabs/tailwindcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/tailwindlabs/tailwindcss/commits/v4.2.2/packages/@tailwindcss-postcss)

Updates `esbuild` from 0.27.3 to 0.27.4
- [Release notes](https://github.com/evanw/esbuild/releases)
- [Changelog](https://github.com/evanw/esbuild/blob/main/CHANGELOG.md)
- [Commits](https://github.com/evanw/esbuild/compare/v0.27.3...v0.27.4)

Updates `tailwindcss` from 4.2.1 to 4.2.2
- [Release notes](https://github.com/tailwindlabs/tailwindcss/releases)
- [Changelog](https://github.com/tailwindlabs/tailwindcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/tailwindlabs/tailwindcss/commits/v4.2.2/packages/tailwindcss)

---
updated-dependencies:
- dependency-name: "@tailwindcss/postcss"
  dependency-version: 4.2.2
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-patch-updates
- dependency-name: esbuild
  dependency-version: 0.27.4
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-patch-updates
- dependency-name: tailwindcss
  dependency-version: 4.2.2
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-patch-updates
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-30 01:35:59 +00:00
dependabot[bot] 11a6f1f47f Bump sigstore/cosign-installer from 4.1.0 to 4.1.1
Bumps [sigstore/cosign-installer](https://github.com/sigstore/cosign-installer) from 4.1.0 to 4.1.1.
- [Release notes](https://github.com/sigstore/cosign-installer/releases)
- [Commits](https://github.com/sigstore/cosign-installer/compare/ba7bc0a3fef59531c69a25acd34668d6d3fe6f22...cad07c2e89fa2edd6e2d7bab4c1aa38e53f76003)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-30 01:34:28 +00:00
Owen fcf92d4e2c Add basic provisioning room v1 and update keys 2026-03-29 16:28:51 -07:00
Owen 77cef554be Provisioning room basics done 2026-03-29 14:20:58 -07:00
Owen 9dc9b6a2c3 Merge branch 'logging-provision' into dev 2026-03-29 13:59:14 -07:00
Owen 9808a48da0 Merge branch 'multi-role' into dev 2026-03-29 13:55:23 -07:00
Milo Schwartz 8a6960d9c3 Merge pull request #2670 from Fredkiss3/feat/selector-filtering
Feat: selector filtering
2026-03-29 12:26:51 -07:00
Milo Schwartz d966ef66e1 Merge pull request #2643 from Fredkiss3/fix/wireguard-site-ip
fix: Display actual values for WireGuard site credentials
2026-03-29 12:23:16 -07:00
Milo Schwartz ed97cf5d97 Merge pull request #2697 from Fredkiss3/feat/modify-private-resource-niceid
feat: edit niceid in private resources
2026-03-29 12:18:21 -07:00
Owen a3b088f8d2 Merge branch 'dev' into logging-provision 2026-03-29 12:18:13 -07:00
miloschwartz 2828dee94c support multi role on create user and invites 2026-03-29 12:11:22 -07:00
Owen Schwartz bdc45887f9 Add chainId to dedup messages (#2737)
* ChainId send through on sensitive messages
2026-03-29 12:08:29 -07:00
miloschwartz ee6fb34906 Merge branch 'multi-role' of https://github.com/fosrl/pangolin into multi-role 2026-03-29 12:01:55 -07:00
miloschwartz bff2ba7cc2 add learn more link 2026-03-29 11:34:07 -07:00
Owen 8e821b397f Add migration 2026-03-28 21:41:21 -07:00
Owen 6f71af278e Add basic migration files 2026-03-28 21:29:32 -07:00
Owen 757bb39622 Support overriding badger for testing 2026-03-28 21:24:13 -07:00
Owen 00ef6d617f Handle the roles better in the verify session 2026-03-28 21:24:13 -07:00
miloschwartz d1b2105c80 support search in tags input 2026-03-28 18:29:40 -07:00
miloschwartz 50ee28b1f7 fix no admin user in roles dropdown 2026-03-28 18:23:07 -07:00
miloschwartz ba529ad14e hide google and azure idp properly 2026-03-28 18:20:56 -07:00
miloschwartz 6ab0555148 respect full rbac feature in auto provisioning 2026-03-28 18:09:36 -07:00
miloschwartz c6f269b3fa set roles 1:1 on auto provision 2026-03-28 17:29:01 -07:00
dependabot[bot] 8e160902af Bump path-to-regexp from 8.3.0 to 8.4.0
Bumps [path-to-regexp](https://github.com/pillarjs/path-to-regexp) from 8.3.0 to 8.4.0.
- [Release notes](https://github.com/pillarjs/path-to-regexp/releases)
- [Changelog](https://github.com/pillarjs/path-to-regexp/blob/master/History.md)
- [Commits](https://github.com/pillarjs/path-to-regexp/compare/v8.3.0...v8.4.0)

---
updated-dependencies:
- dependency-name: path-to-regexp
  dependency-version: 8.4.0
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-28 18:37:06 +00:00
miloschwartz 7bcb852dba add google and azure templates to global idp 2026-03-27 18:10:19 -07:00
miloschwartz ed604c8810 Merge branch 'multi-role' of https://github.com/fosrl/pangolin into multi-role 2026-03-27 17:35:50 -07:00
miloschwartz bea20674a8 support policy buildiner in global idp 2026-03-27 17:35:35 -07:00
Owen 177926932b Update hybrid for multi role 2026-03-27 17:07:58 -07:00
Owen 04dfbd0a14 Chainid send through 2026-03-27 12:04:41 -07:00
dependabot[bot] 06f840a680 Bump brace-expansion
Bumps  and [brace-expansion](https://github.com/juliangruber/brace-expansion). These dependencies needed to be updated together.

Updates `brace-expansion` from 5.0.4 to 5.0.5
- [Release notes](https://github.com/juliangruber/brace-expansion/releases)
- [Commits](https://github.com/juliangruber/brace-expansion/compare/v5.0.4...v5.0.5)

Updates `brace-expansion` from 1.1.12 to 1.1.13
- [Release notes](https://github.com/juliangruber/brace-expansion/releases)
- [Commits](https://github.com/juliangruber/brace-expansion/compare/v5.0.4...v5.0.5)

---
updated-dependencies:
- dependency-name: brace-expansion
  dependency-version: 5.0.5
  dependency-type: indirect
- dependency-name: brace-expansion
  dependency-version: 1.1.13
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-27 14:43:09 +00:00
dependabot[bot] 5ddcfeb506 Bump nodemailer from 8.0.1 to 8.0.4
Bumps [nodemailer](https://github.com/nodemailer/nodemailer) from 8.0.1 to 8.0.4.
- [Release notes](https://github.com/nodemailer/nodemailer/releases)
- [Changelog](https://github.com/nodemailer/nodemailer/blob/master/CHANGELOG.md)
- [Commits](https://github.com/nodemailer/nodemailer/compare/v8.0.1...v8.0.4)

---
updated-dependencies:
- dependency-name: nodemailer
  dependency-version: 8.0.4
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-27 06:56:52 +00:00
Owen a143b7de7c Merge branch 'multi-role' of github.com:fosrl/pangolin into multi-role 2026-03-26 21:47:13 -07:00
Owen 63372b174f Merge branch 'dev' into multi-role 2026-03-26 21:46:29 -07:00
miloschwartz ad7d68d2b4 basic idp mapping builder 2026-03-26 21:46:01 -07:00
Owen e05af54f76 Use standard component 2026-03-26 21:36:51 -07:00
dependabot[bot] 914e95e47f Bump yaml from 2.8.2 to 2.8.3
Bumps [yaml](https://github.com/eemeli/yaml) from 2.8.2 to 2.8.3.
- [Release notes](https://github.com/eemeli/yaml/releases)
- [Commits](https://github.com/eemeli/yaml/compare/v2.8.2...v2.8.3)

---
updated-dependencies:
- dependency-name: yaml
  dependency-version: 2.8.3
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-27 02:36:44 +00:00
miloschwartz 13eadeaa8f support legacy one role per user 2026-03-26 18:19:10 -07:00
Owen 19a686b3e4 Add restrictions around provisioning key 2026-03-26 16:49:43 -07:00
miloschwartz d046084e84 delete role move to new role 2026-03-26 16:44:30 -07:00
miloschwartz e13a076939 ui improvements 2026-03-26 16:37:31 -07:00
Owen b4ca6432db Fix double id 2026-03-26 16:24:44 -07:00
dependabot[bot] 5b9efc3c5f Bump picomatch
Bumps  and [picomatch](https://github.com/micromatch/picomatch). These dependencies needed to be updated together.

Updates `picomatch` from 2.3.1 to 2.3.2
- [Release notes](https://github.com/micromatch/picomatch/releases)
- [Changelog](https://github.com/micromatch/picomatch/blob/master/CHANGELOG.md)
- [Commits](https://github.com/micromatch/picomatch/compare/2.3.1...2.3.2)

Updates `picomatch` from 4.0.3 to 4.0.4
- [Release notes](https://github.com/micromatch/picomatch/releases)
- [Changelog](https://github.com/micromatch/picomatch/blob/master/CHANGELOG.md)
- [Commits](https://github.com/micromatch/picomatch/compare/2.3.1...2.3.2)

---
updated-dependencies:
- dependency-name: picomatch
  dependency-version: 2.3.2
  dependency-type: indirect
- dependency-name: picomatch
  dependency-version: 4.0.4
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-26 08:51:09 +00:00
Owen Schwartz 6d7a19b0a0 Merge pull request #2716 from fosrl/patch-1
Add typecasts
2026-03-25 22:12:59 -07:00
Owen 6b3a6fa380 Add typecasts 2026-03-25 22:11:56 -07:00
Owen Schwartz e2a65b4b74 Merge pull request #2715 from fosrl/batch-band
Batch set bandwidth
2026-03-25 21:54:44 -07:00
Owen 1f01108b62 Batch set bandwidth 2026-03-25 21:53:20 -07:00
Owen c80c7df1d0 Batch set bandwidth 2026-03-25 20:44:08 -07:00
Owen 99a064b77a Fix import problems 2026-03-25 20:44:08 -07:00
Owen 9b84623d0c Cache token for thundering hurd 2026-03-25 20:44:08 -07:00
Owen 6bb6cf8a48 Clean up 2026-03-25 20:44:08 -07:00
Owen 348fcbcabf Try to solve th problem 2026-03-25 20:44:08 -07:00
Owen 1f4cde5f7f Add license script 2026-03-25 20:44:08 -07:00
Owen 3e3b02021c Add ssh access log 2026-03-25 20:44:08 -07:00
Owen 17eb93d045 Add better pooling controls 2026-03-25 20:44:08 -07:00
Owen 660420ddef Disable everything if not paid 2026-03-25 20:44:08 -07:00
Owen 395cab795c Batch set bandwidth 2026-03-25 20:35:21 -07:00
miloschwartz 0fecbe704b Merge branch 'dev' into multi-role 2026-03-24 22:01:13 -07:00
Owen ce59a8a52b Merge branch 'main' into dev 2026-03-24 20:38:16 -07:00
miloschwartz 2091b5f359 Merge branch 'logging-provision' of https://github.com/fosrl/pangolin into logging-provision 2026-03-24 20:30:14 -07:00
Owen Schwartz 62c63ddcaa Merge pull request #2710 from fosrl/thundering-herd
thundering herd
2026-03-24 20:29:01 -07:00
Owen dfd604c781 Fix import problems 2026-03-24 20:27:34 -07:00
miloschwartz 3525b367b3 move to private routes 2026-03-24 20:27:15 -07:00
Owen 0b5b6ed5a3 Adjust register endpoint 2026-03-24 18:26:10 -07:00
Owen 6fe9494df4 Merge branch 'logging-provision' of github.com:fosrl/pangolin into logging-provision 2026-03-24 18:17:42 -07:00
Owen b2eab95a3b Pass at first endpoints 2026-03-24 18:17:33 -07:00
Owen 38d30b0214 Add license script 2026-03-24 18:13:57 -07:00
Owen c96c5e8ae8 Cache token for thundering hurd 2026-03-24 18:12:51 -07:00
Owen 6f71e9f0f2 Clean up 2026-03-24 17:55:14 -07:00
Owen d17ec6dc1f Try to solve th problem 2026-03-24 17:39:43 -07:00
miloschwartz 212b7a104f Merge branch 'logging-provision' of https://github.com/fosrl/pangolin into logging-provision 2026-03-24 17:01:36 -07:00
miloschwartz d21dfb750e ui for provisioning key 2026-03-24 17:01:20 -07:00
Owen Schwartz c36a019f5d Merge pull request #2709 from fosrl/pool-update
Update pool and disable idp
2026-03-24 16:48:28 -07:00
Owen cf2dfdea5b Add better pooling controls 2026-03-24 16:38:50 -07:00
Owen 985e1bb9ab Disable everything if not paid 2026-03-24 16:38:46 -07:00
Owen fff38aac85 Add ssh access log 2026-03-24 16:26:56 -07:00
miloschwartz 7db58f920c add site provisioning key crud 2026-03-24 16:19:00 -07:00
Fred KISSIE e9b16b8801 Merge branch 'dev' into feat/modify-private-resource-niceid 2026-03-25 00:13:35 +01:00
Owen 5a2a97b23a Add better pooling controls 2026-03-24 16:12:13 -07:00
Owen 5b894e8682 Disable everything if not paid 2026-03-24 16:01:54 -07:00
Fred KISSIE 84925f724d 💄 update UI 2026-03-24 23:43:01 +01:00
Owen 7b78b91449 Fix resource link 2026-03-23 22:00:53 -07:00
Owen f9bff5954f Add filters and refine table and query 2026-03-23 21:49:22 -07:00
Owen 2c6e9507b5 Connection log page working 2026-03-23 21:41:53 -07:00
Owen 6471571bc6 Add ui for connection logs 2026-03-23 20:18:03 -07:00
Owen fe40ea58c1 Source client info into schema 2026-03-23 20:05:54 -07:00
Owen 0d4edcd1c7 make private 2026-03-23 17:23:51 -07:00
Owen 7d8797840a Add connection log 2026-03-23 17:01:34 -07:00
Owen Schwartz 19f8c1772f Merge pull request #2698 from fosrl/msg-opt
Improve proxy list message size
2026-03-23 16:05:24 -07:00
Owen 37d331e813 Update version 2026-03-23 16:05:05 -07:00
Owen c660df55cd Merge branch 'dev' into msg-opt 2026-03-23 16:00:50 -07:00
Fred KISSIE 60982bf19f 🚧 edit niceid in private resources 2026-03-23 22:55:59 +01:00
Owen Schwartz 7c8b865379 Merge pull request #2695 from noe-charmet/redis-password-env
Allow setting Redis password from env
2026-03-23 12:02:45 -07:00
Noe Charmet 3cca0c09c0 Allow setting Redis password from env 2026-03-23 11:18:55 +01:00
Owen Schwartz 85335bfecc Merge pull request #2685 from fosrl/dev
1.16.2-s.16
2026-03-21 10:47:18 -07:00
Owen 7c2b4f422a Merge branch 'main' into dev 2026-03-21 10:45:13 -07:00
Owen ad2a0ae127 Use the log database in hybrid as well 2026-03-21 10:42:31 -07:00
dependabot[bot] 871f14ef3a Bump flatted from 3.3.3 to 3.4.2
Bumps [flatted](https://github.com/WebReflection/flatted) from 3.3.3 to 3.4.2.
- [Commits](https://github.com/WebReflection/flatted/compare/v3.3.3...v3.4.2)

---
updated-dependencies:
- dependency-name: flatted
  dependency-version: 3.4.2
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-21 11:17:41 +00:00
miloschwartz 6c2c620c99 set cache ttl and default ttl 2026-03-20 17:52:07 -07:00
miloschwartz f643abf19a dont show create org for oidc users 2026-03-20 16:04:00 -07:00
Owen Schwartz a1729033cf Merge pull request #2682 from fosrl/dev
Fix offline issue
2026-03-20 15:31:38 -07:00
Owen 7311766512 Fix offline issue 2026-03-20 15:30:41 -07:00
Owen Schwartz 17105f3a51 Merge pull request #2681 from fosrl/dev
Extend santize into hybrid
2026-03-20 14:33:23 -07:00
Owen edcfbd26e4 Merge branch 'dev' of github.com:fosrl/pangolin into dev 2026-03-20 14:31:27 -07:00
Owen 0c4d9ea164 Extend santize into hybrid 2026-03-20 14:31:12 -07:00
Owen Schwartz a5a5224f5c Merge pull request #2680 from fosrl/dev
Translation updates
2026-03-20 13:52:11 -07:00
Owen Schwartz 8773f7c0a7 Merge pull request #2679 from fosrl/crowdin_dev
New Crowdin updates
2026-03-20 13:51:16 -07:00
Owen Schwartz f385bc2d22 Merge pull request #2678 from fosrl/dev
1.16.2-s.14
2026-03-20 11:25:03 -07:00
Owen Schwartz a8c9d2e7e6 New translations en-us.json (Spanish) 2026-03-20 11:16:17 -07:00
Owen Schwartz db3f90318b New translations en-us.json (Norwegian Bokmal) 2026-03-20 11:16:15 -07:00
Owen Schwartz 2d4d0df5ca New translations en-us.json (Chinese Simplified) 2026-03-20 11:16:14 -07:00
Owen Schwartz 569ebc671d New translations en-us.json (Turkish) 2026-03-20 11:16:12 -07:00
Owen Schwartz 8c8e4e6233 New translations en-us.json (Russian) 2026-03-20 11:16:11 -07:00
Owen Schwartz c7901ef74b New translations en-us.json (Portuguese) 2026-03-20 11:16:09 -07:00
Owen Schwartz be3bd72c1b New translations en-us.json (Polish) 2026-03-20 11:16:08 -07:00
Owen Schwartz 73d1f9288d New translations en-us.json (Dutch) 2026-03-20 11:16:06 -07:00
Owen Schwartz fb7e9f6898 New translations en-us.json (Korean) 2026-03-20 11:16:05 -07:00
Owen Schwartz 38e4b3077f New translations en-us.json (Italian) 2026-03-20 11:16:03 -07:00
Owen Schwartz 312cdc563b New translations en-us.json (German) 2026-03-20 11:16:02 -07:00
Owen Schwartz 48ff6dd705 New translations en-us.json (Czech) 2026-03-20 11:16:01 -07:00
Owen Schwartz 695e831090 New translations en-us.json (Bulgarian) 2026-03-20 11:15:59 -07:00
Owen Schwartz 046b431bb8 New translations en-us.json (French) 2026-03-20 11:15:58 -07:00
Owen ce2704fc1a Merge branch 'dev' of github.com:fosrl/pangolin into dev 2026-03-20 11:04:45 -07:00
Owen Schwartz 7e89b36188 Merge pull request #2677 from fosrl/crowdin_dev
New Crowdin updates
2026-03-20 11:04:37 -07:00
Owen 222dd6bba3 Santize inserts 2026-03-20 10:27:18 -07:00
Owen Schwartz ca9ab65228 New translations en-us.json (Spanish) 2026-03-19 21:38:08 -07:00
Owen Schwartz ee4e8f7029 New translations en-us.json (Norwegian Bokmal) 2026-03-19 21:38:07 -07:00
Owen Schwartz f86a1eb32b New translations en-us.json (Chinese Simplified) 2026-03-19 21:38:06 -07:00
Owen Schwartz ffd648ed74 New translations en-us.json (Turkish) 2026-03-19 21:38:05 -07:00
Owen Schwartz b2b72169fd New translations en-us.json (Russian) 2026-03-19 21:38:03 -07:00
Owen Schwartz 76746fb6e1 New translations en-us.json (Portuguese) 2026-03-19 21:38:02 -07:00
Owen Schwartz 6258787c73 New translations en-us.json (Polish) 2026-03-19 21:38:00 -07:00
Owen Schwartz 720080e487 New translations en-us.json (Dutch) 2026-03-19 21:37:59 -07:00
Owen Schwartz 46ad1317e4 New translations en-us.json (Korean) 2026-03-19 21:37:58 -07:00
Owen Schwartz cd28720e46 New translations en-us.json (Italian) 2026-03-19 21:37:56 -07:00
Owen Schwartz 38af02ad3c New translations en-us.json (German) 2026-03-19 21:37:55 -07:00
Owen Schwartz 5eed547f91 New translations en-us.json (Czech) 2026-03-19 21:37:54 -07:00
Owen Schwartz d363ee02ed New translations en-us.json (Bulgarian) 2026-03-19 21:37:53 -07:00
Owen Schwartz 594ee31f43 New translations en-us.json (French) 2026-03-19 21:37:51 -07:00
Owen 56e25d01ae Fix spelling mistake 2026-03-19 20:54:05 -07:00
Fred KISSIE e0fa5607e5 push 2026-03-20 04:37:57 +01:00
Fred KISSIE 572c9bf319 Merge branch 'dev' into feat/selector-filtering 2026-03-20 04:24:48 +01:00
Fred KISSIE 52cac4aa21 🚚 rename component 2026-03-20 04:17:35 +01:00
Fred KISSIE e358d12765 ♻️ submit 2026-03-20 04:15:18 +01:00
Fred KISSIE 02697e27a4 ♻️ refactor 2026-03-20 04:02:51 +01:00
Fred KISSIE ce58e71c44 ♻️ make machine selector a multi-combobox 2026-03-20 03:59:10 +01:00
Owen Schwartz d9766b0f99 New translations en-us.json (Spanish) 2026-03-19 14:39:09 -07:00
Owen Schwartz eeaa1d56ad New translations en-us.json (Norwegian Bokmal) 2026-03-19 14:39:07 -07:00
Owen Schwartz e7f5bc585c New translations en-us.json (Chinese Simplified) 2026-03-19 14:39:06 -07:00
Owen Schwartz 4f26fb7750 New translations en-us.json (Turkish) 2026-03-19 14:39:04 -07:00
Owen Schwartz cdbc190bfc New translations en-us.json (Russian) 2026-03-19 14:39:03 -07:00
Owen Schwartz 1b1f9ab4cf New translations en-us.json (Portuguese) 2026-03-19 14:39:02 -07:00
Owen Schwartz 2efe6cfdb3 New translations en-us.json (Polish) 2026-03-19 14:39:00 -07:00
Owen Schwartz 517c607ecf New translations en-us.json (Dutch) 2026-03-19 14:38:59 -07:00
Owen Schwartz 802e8f7a22 New translations en-us.json (Korean) 2026-03-19 14:38:57 -07:00
Owen Schwartz c7cfe2efcb New translations en-us.json (Italian) 2026-03-19 14:38:56 -07:00
Owen Schwartz ae1f36f39a New translations en-us.json (German) 2026-03-19 14:38:54 -07:00
Owen Schwartz a479ef28ac New translations en-us.json (Czech) 2026-03-19 14:38:53 -07:00
Owen Schwartz ce2cf50b5a New translations en-us.json (Bulgarian) 2026-03-19 14:38:52 -07:00
Owen Schwartz f48d01acde New translations en-us.json (French) 2026-03-19 14:38:50 -07:00
Owen 991fed93ee Add warning when creating resource with provided 2026-03-19 14:26:14 -07:00
Owen 26ab63d0e4 Adjust remote node language 2026-03-19 12:10:58 -07:00
Fred KISSIE e15703164d ♻️ resource selector in create share link form 2026-03-19 04:44:24 +01:00
Fred KISSIE 8f33e25782 ♻️ use site selector on private resources 2026-03-19 01:18:27 +01:00
Fred KISSIE 722595c131 ♻️ make site selector popover its own component 2026-03-19 00:35:26 +01:00
Owen Schwartz 4843268537 Merge pull request #2552 from huzky-v/feat-add-bandwidth-reset-api
feat: Adding an organization sites bandwidth reset API
2026-03-18 16:17:43 -07:00
Fred KISSIE c9be84a8a8 Merge branch 'dev' into feat/selector-filtering 2026-03-18 23:38:25 +01:00
Owen Schwartz 03288d2a60 Merge pull request #2667 from LaurenceJJones/feature/newt-ipv6-format-endpoint
fix(newt): Format ipv6 targets for go
2026-03-18 15:34:36 -07:00
Owen Schwartz f60ae13e4e Merge pull request #2668 from LaurenceJJones/docs/improve-cloud-messaging
chore(readme): Reorder and promote cloud
2026-03-18 15:32:53 -07:00
Owen Schwartz e72697f8b8 Merge pull request #2669 from fosrl/dependabot/npm_and_yarn/multi-577d045ab6
Bump fast-xml-parser and @aws-sdk/xml-builder
2026-03-18 15:30:46 -07:00
dependabot[bot] 0c3dc1ad14 Bump fast-xml-parser and @aws-sdk/xml-builder
Bumps [fast-xml-parser](https://github.com/NaturalIntelligence/fast-xml-parser) and [@aws-sdk/xml-builder](https://github.com/aws/aws-sdk-js-v3/tree/HEAD/packages-internal/xml-builder). These dependencies needed to be updated together.

Updates `fast-xml-parser` from 5.4.1 to 5.5.6
- [Release notes](https://github.com/NaturalIntelligence/fast-xml-parser/releases)
- [Changelog](https://github.com/NaturalIntelligence/fast-xml-parser/blob/master/CHANGELOG.md)
- [Commits](https://github.com/NaturalIntelligence/fast-xml-parser/compare/v5.4.1...v5.5.6)

Updates `@aws-sdk/xml-builder` from 3.972.10 to 3.972.12
- [Release notes](https://github.com/aws/aws-sdk-js-v3/releases)
- [Changelog](https://github.com/aws/aws-sdk-js-v3/blob/main/packages-internal/xml-builder/CHANGELOG.md)
- [Commits](https://github.com/aws/aws-sdk-js-v3/commits/HEAD/packages-internal/xml-builder)

---
updated-dependencies:
- dependency-name: fast-xml-parser
  dependency-version: 5.5.6
  dependency-type: indirect
- dependency-name: "@aws-sdk/xml-builder"
  dependency-version: 3.972.12
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-18 22:30:09 +00:00
Owen Schwartz 840fe86f78 Merge pull request #2633 from fosrl/dependabot/npm_and_yarn/eslint-10.0.3
Bump eslint from 9.39.2 to 10.0.3
2026-03-18 15:28:51 -07:00
Owen Schwartz e079927a5b Merge pull request #2579 from fosrl/dependabot/github_actions/actions/setup-go-6.3.0
Bump actions/setup-go from 6.2.0 to 6.3.0
2026-03-18 15:28:26 -07:00
dependabot[bot] 63379964fa Bump eslint from 9.39.2 to 10.0.3
Bumps [eslint](https://github.com/eslint/eslint) from 9.39.2 to 10.0.3.
- [Release notes](https://github.com/eslint/eslint/releases)
- [Commits](https://github.com/eslint/eslint/compare/v9.39.2...v10.0.3)

---
updated-dependencies:
- dependency-name: eslint
  dependency-version: 10.0.3
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-18 22:28:22 +00:00
Owen Schwartz 0cfaf6ed7f Merge pull request #2580 from fosrl/dependabot/github_actions/actions/upload-artifact-7.0.0
Bump actions/upload-artifact from 6.0.0 to 7.0.0
2026-03-18 15:28:12 -07:00
Owen Schwartz 043ee9e9d2 Merge pull request #2620 from fosrl/dependabot/github_actions/actions/setup-node-6.3.0
Bump actions/setup-node from 6.2.0 to 6.3.0
2026-03-18 15:27:52 -07:00
dependabot[bot] 1d5dfd6db2 Bump github.com/charmbracelet/huh from 0.8.0 to 1.0.0 in /install
Bumps [github.com/charmbracelet/huh](https://github.com/charmbracelet/huh) from 0.8.0 to 1.0.0.
- [Release notes](https://github.com/charmbracelet/huh/releases)
- [Commits](https://github.com/charmbracelet/huh/compare/v0.8.0...v1.0.0)

---
updated-dependencies:
- dependency-name: github.com/charmbracelet/huh
  dependency-version: 1.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-18 22:27:43 +00:00
Owen Schwartz b63e3e5888 Merge pull request #2621 from fosrl/dependabot/github_actions/docker/login-action-4.0.0
Bump docker/login-action from 3.7.0 to 4.0.0
2026-03-18 15:27:26 -07:00
Owen Schwartz 4f82470506 Merge pull request #2629 from fosrl/dependabot/npm_and_yarn/prod-minor-updates-47a8475ba0
Bump the prod-minor-updates group across 1 directory with 3 updates
2026-03-18 15:26:14 -07:00
Owen Schwartz 40e21b6f28 Merge pull request #2641 from fosrl/dependabot/go_modules/install/minor-updates-a98db8910e
Bump golang.org/x/term from 0.40.0 to 0.41.0 in /install in the minor-updates group
2026-03-18 15:25:44 -07:00
Owen Schwartz 67fab1928d Merge pull request #2656 from fosrl/dependabot/github_actions/sigstore/cosign-installer-4.1.0
Bump sigstore/cosign-installer from 4.0.0 to 4.1.0
2026-03-18 15:25:31 -07:00
Owen Schwartz eb98374566 Merge pull request #2666 from fosrl/dependabot/npm_and_yarn/dev-patch-updates-6a5ea32984
Bump the dev-patch-updates group across 1 directory with 5 updates
2026-03-18 15:25:14 -07:00
miloschwartz 1169b68619 fix more info content on member page 2026-03-18 12:18:18 -07:00
Laurence 6c83e78256 chore(readme): Reorder and promote cloud
Simply moving the items around and improve the messaging around the cloud
2026-03-18 16:06:50 +00:00
Laurence d3bfd67738 fix(newt): Format ipv6 targets for go
We added support https://github.com/fosrl/newt/releases/tag/1.10.3 for ipv6 targets from newt -> application, but we need to ensure that we handle if user provides a none bracketed ipv6 string
2026-03-18 13:26:38 +00:00
dependabot[bot] 0908f0f057 Bump the prod-minor-updates group across 1 directory with 3 updates
Bumps the prod-minor-updates group with 3 updates in the / directory: [@aws-sdk/client-s3](https://github.com/aws/aws-sdk-js-v3/tree/HEAD/clients/client-s3), [@simplewebauthn/browser](https://github.com/MasterKale/SimpleWebAuthn/tree/HEAD/packages/browser) and [@simplewebauthn/server](https://github.com/MasterKale/SimpleWebAuthn/tree/HEAD/packages/server).


Updates `@aws-sdk/client-s3` from 3.1004.0 to 3.1006.0
- [Release notes](https://github.com/aws/aws-sdk-js-v3/releases)
- [Changelog](https://github.com/aws/aws-sdk-js-v3/blob/main/clients/client-s3/CHANGELOG.md)
- [Commits](https://github.com/aws/aws-sdk-js-v3/commits/v3.1006.0/clients/client-s3)

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

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

---
updated-dependencies:
- dependency-name: "@aws-sdk/client-s3"
  dependency-version: 3.1006.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: prod-minor-updates
- dependency-name: "@simplewebauthn/browser"
  dependency-version: 13.3.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: prod-minor-updates
- dependency-name: "@simplewebauthn/server"
  dependency-version: 13.3.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: prod-minor-updates
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-18 01:37:21 +00:00
dependabot[bot] 2785449c7a Bump the dev-patch-updates group across 1 directory with 5 updates
Bumps the dev-patch-updates group with 5 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [@react-email/preview-server](https://github.com/resend/react-email/tree/HEAD/packages/preview-server) | `5.2.8` | `5.2.10` |
| [drizzle-kit](https://github.com/drizzle-team/drizzle-orm) | `0.31.9` | `0.31.10` |
| [eslint-config-next](https://github.com/vercel/next.js/tree/HEAD/packages/eslint-config-next) | `16.1.6` | `16.1.7` |
| [postcss](https://github.com/postcss/postcss) | `8.5.6` | `8.5.8` |
| [react-email](https://github.com/resend/react-email/tree/HEAD/packages/react-email) | `5.2.8` | `5.2.10` |



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

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

Updates `eslint-config-next` from 16.1.6 to 16.1.7
- [Release notes](https://github.com/vercel/next.js/releases)
- [Changelog](https://github.com/vercel/next.js/blob/canary/release.js)
- [Commits](https://github.com/vercel/next.js/commits/v16.1.7/packages/eslint-config-next)

Updates `postcss` from 8.5.6 to 8.5.8
- [Release notes](https://github.com/postcss/postcss/releases)
- [Changelog](https://github.com/postcss/postcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/postcss/postcss/compare/8.5.6...8.5.8)

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

---
updated-dependencies:
- dependency-name: "@react-email/preview-server"
  dependency-version: 5.2.10
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-patch-updates
- dependency-name: drizzle-kit
  dependency-version: 0.31.10
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-patch-updates
- dependency-name: eslint-config-next
  dependency-version: 16.1.7
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-patch-updates
- dependency-name: postcss
  dependency-version: 8.5.8
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-patch-updates
- dependency-name: react-email
  dependency-version: 5.2.10
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-patch-updates
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-18 01:36:08 +00:00
dependabot[bot] d2419ba572 Bump golang.org/x/term in /install in the minor-updates group
Bumps the minor-updates group in /install with 1 update: [golang.org/x/term](https://github.com/golang/term).


Updates `golang.org/x/term` from 0.40.0 to 0.41.0
- [Commits](https://github.com/golang/term/compare/v0.40.0...v0.41.0)

---
updated-dependencies:
- dependency-name: golang.org/x/term
  dependency-version: 0.41.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: minor-updates
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-18 01:34:08 +00:00
miloschwartz d44292cf33 pass access token params to badger 2026-03-17 16:57:31 -07:00
Fred KISSIE 435cae06a2 ♻️ refactor 2026-03-17 04:16:24 +01:00
Fred KISSIE 18ed38889f ♻️ filter sites server side in resource target 2026-03-17 04:07:02 +01:00
Owen Schwartz aed86ce4ba Merge pull request #2663 from fosrl/dev
change route name
2026-03-16 20:03:56 -07:00
miloschwartz 2c2be50b19 change route name 2026-03-16 20:02:57 -07:00
Owen Schwartz e2db4c6246 Merge pull request #2662 from fosrl/batch-add-client-to-resources
batch add client to resources
2026-03-16 19:53:47 -07:00
miloschwartz c4839fee08 Merge branch 'dev' into batch-add-client-to-resources 2026-03-16 17:58:37 -07:00
miloschwartz 965b7026f0 add batch endpoint 2026-03-16 17:58:20 -07:00
Owen e14e15fcbb Revert: Also update lastPing for legacy 2026-03-16 17:47:06 -07:00
Owen Schwartz 4ca5acf158 Merge pull request #2660 from fosrl/dev
Also update lastPing for legacy
2026-03-16 17:13:10 -07:00
Owen ea41fcc566 Also update lastPing for legacy 2026-03-16 17:12:37 -07:00
Owen Schwartz 5736c1d8ce Merge pull request #2659 from fosrl/dev
Small improvements
2026-03-16 16:37:26 -07:00
Owen d142366dd9 Merge branch 'main' into dev 2026-03-16 16:32:28 -07:00
Owen bab09dff95 Add better metadata to ssh 2026-03-16 15:33:21 -07:00
Owen 23d3345ab9 Reduce writes 2026-03-16 14:37:27 -07:00
Owen Schwartz 09a64815d4 Merge pull request #2657 from fosrl/hotfix-jit
Fix jit on by default
2026-03-15 22:02:12 -07:00
Owen 6d5f969798 Fix jit on by default 2026-03-15 22:01:39 -07:00
dependabot[bot] 10349932f4 Bump sigstore/cosign-installer from 4.0.0 to 4.1.0
Bumps [sigstore/cosign-installer](https://github.com/sigstore/cosign-installer) from 4.0.0 to 4.1.0.
- [Release notes](https://github.com/sigstore/cosign-installer/releases)
- [Commits](https://github.com/sigstore/cosign-installer/compare/faadad0cce49287aee09b3a48701e75088a2c6ad...ba7bc0a3fef59531c69a25acd34668d6d3fe6f22)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-16 01:35:18 +00:00
Owen Schwartz 9c430b37aa Merge pull request #2655 from fosrl/dev
1.16.2-s.8
2026-03-15 16:47:09 -07:00
Shlee ad3fe2fa76 Update traefik_config.yml 2026-03-15 19:39:36 +10:30
Shlee 863eb8efe9 Update docker-compose.yml 2026-03-15 19:37:15 +10:30
Owen 86bba494fe Disable intervals in saas 2026-03-14 16:03:43 -07:00
Owen 1a43f1ef4b Handle newt online offline with websocket 2026-03-14 11:59:20 -07:00
Owen 75ab074805 Attempt to improve handling bandwidth tracking 2026-03-13 12:06:01 -07:00
Owen dc4e0253de Add message compression for large messages 2026-03-13 11:46:03 -07:00
Laurence 47a99e35ee feat(installer): add default install directory with existing install detection
- Default to /opt/pangolin for new installations
  - Check current directory and /opt/pangolin for existing installs
  - Prompt to use existing install if found at default location
  - Offer to change directory ownership when running via sudo
  - Create installation directory if it doesn't exist
2026-03-13 11:38:00 +00:00
Owen cccf236042 Add optional compression 2026-03-12 17:49:21 -07:00
Owen 63fd63c65c Send less data down 2026-03-12 17:27:15 -07:00
Owen beee1d692d revert: telemetry comment 2026-03-12 17:11:13 -07:00
Owen fde786ca84 Add todo 2026-03-12 17:10:46 -07:00
Owen 3086fdd064 Merge branch 'dev' into jit 2026-03-12 16:58:23 -07:00
Owen 6c30f6db31 Dont send site if it missing public key 2026-03-12 16:33:33 -07:00
Fred KISSIE 84b082e194 ♻️ show actual values for wireguard site credentials whenever possible 2026-03-12 23:36:35 +01:00
Owen f021b73458 Add alert about domain error 2026-03-11 18:00:23 -07:00
Owen 74f4751bcc Dont show raw resource option unless remote node 2026-03-11 17:47:15 -07:00
Owen e5bce4e180 Merge branch 'main' into dev 2026-03-11 15:55:59 -07:00
Owen 9b0e7b381c Fix error to gerbil 2026-03-11 15:49:03 -07:00
Owen 90afe5a7ac Log errors 2026-03-11 15:42:40 -07:00
Owen b24de85157 Handle gerbil rejecting 0
Closes #2605
2026-03-11 15:06:26 -07:00
Owen eda43dffe1 Fix not pulling wildcard cert updates 2026-03-11 15:06:26 -07:00
Owen 82c9a1eb70 Add demo link 2026-03-11 15:06:26 -07:00
Owen a3d4553d14 Merge branch 'main' into dev 2026-03-11 14:53:55 -07:00
Owen 1cc5f59f66 Implement email and ip banning 2026-03-11 11:42:31 -07:00
Owen 4e2d88efdd Add some logging to debug 2026-03-11 11:42:28 -07:00
Owen 4975cabb2c Use native drizzle count 2026-03-11 11:42:28 -07:00
Owen 225591094f Clean up 2026-03-11 11:42:28 -07:00
Owen 82f88f2cd3 Reorder delete 2026-03-11 11:42:28 -07:00
Owen 99e6bd31b6 Bump dompurify 2026-03-10 16:47:03 -07:00
Owen 5c50590d7b Bump esbuild 2026-03-10 16:47:03 -07:00
Owen 072c89e704 Bump dompurify 2026-03-10 16:43:40 -07:00
Owen dbdff6812d Bump esbuild 2026-03-10 16:31:19 -07:00
dependabot[bot] 42b9d5158d Bump the prod-minor-updates group across 1 directory with 10 updates
Bumps the prod-minor-updates group with 10 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [@aws-sdk/client-s3](https://github.com/aws/aws-sdk-js-v3/tree/HEAD/clients/client-s3) | `3.989.0` | `3.1003.0` |
| [express-rate-limit](https://github.com/express-rate-limit/express-rate-limit) | `8.2.1` | `8.3.0` |
| [ioredis](https://github.com/luin/ioredis) | `5.9.3` | `5.10.0` |
| [lucide-react](https://github.com/lucide-icons/lucide/tree/HEAD/packages/lucide-react) | `0.563.0` | `0.577.0` |
| [pg](https://github.com/brianc/node-postgres/tree/HEAD/packages/pg) | `8.19.0` | `8.20.0` |
| [posthog-node](https://github.com/PostHog/posthog-js/tree/HEAD/packages/node) | `5.26.0` | `5.28.0` |
| [react-day-picker](https://github.com/gpbl/react-day-picker) | `9.13.2` | `9.14.0` |
| [react-icons](https://github.com/react-icons/react-icons) | `5.5.0` | `5.6.0` |
| reodotdev | `1.0.0` | `1.1.0` |
| [stripe](https://github.com/stripe/stripe-node) | `20.3.1` | `20.4.0` |



Updates `@aws-sdk/client-s3` from 3.989.0 to 3.1003.0
- [Release notes](https://github.com/aws/aws-sdk-js-v3/releases)
- [Changelog](https://github.com/aws/aws-sdk-js-v3/blob/main/clients/client-s3/CHANGELOG.md)
- [Commits](https://github.com/aws/aws-sdk-js-v3/commits/v3.1003.0/clients/client-s3)

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

Updates `ioredis` from 5.9.3 to 5.10.0
- [Release notes](https://github.com/luin/ioredis/releases)
- [Changelog](https://github.com/redis/ioredis/blob/main/CHANGELOG.md)
- [Commits](https://github.com/luin/ioredis/compare/v5.9.3...v5.10.0)

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

Updates `pg` from 8.19.0 to 8.20.0
- [Changelog](https://github.com/brianc/node-postgres/blob/master/CHANGELOG.md)
- [Commits](https://github.com/brianc/node-postgres/commits/pg@8.20.0/packages/pg)

Updates `posthog-node` from 5.26.0 to 5.28.0
- [Release notes](https://github.com/PostHog/posthog-js/releases)
- [Changelog](https://github.com/PostHog/posthog-js/blob/main/packages/node/CHANGELOG.md)
- [Commits](https://github.com/PostHog/posthog-js/commits/posthog-node@5.28.0/packages/node)

Updates `react-day-picker` from 9.13.2 to 9.14.0
- [Release notes](https://github.com/gpbl/react-day-picker/releases)
- [Changelog](https://github.com/gpbl/react-day-picker/blob/main/CHANGELOG.md)
- [Commits](https://github.com/gpbl/react-day-picker/compare/v9.13.2...v9.14.0)

Updates `react-icons` from 5.5.0 to 5.6.0
- [Release notes](https://github.com/react-icons/react-icons/releases)
- [Commits](https://github.com/react-icons/react-icons/compare/v5.5.0...v5.6.0)

Updates `reodotdev` from 1.0.0 to 1.1.0

Updates `stripe` from 20.3.1 to 20.4.0
- [Release notes](https://github.com/stripe/stripe-node/releases)
- [Changelog](https://github.com/stripe/stripe-node/blob/master/CHANGELOG.md)
- [Commits](https://github.com/stripe/stripe-node/compare/v20.3.1...v20.4.0)

---
updated-dependencies:
- dependency-name: "@aws-sdk/client-s3"
  dependency-version: 3.1003.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: prod-minor-updates
- dependency-name: express-rate-limit
  dependency-version: 8.3.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: prod-minor-updates
- dependency-name: ioredis
  dependency-version: 5.10.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: prod-minor-updates
- dependency-name: lucide-react
  dependency-version: 0.577.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: prod-minor-updates
- dependency-name: pg
  dependency-version: 8.20.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: prod-minor-updates
- dependency-name: posthog-node
  dependency-version: 5.28.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: prod-minor-updates
- dependency-name: react-day-picker
  dependency-version: 9.14.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: prod-minor-updates
- dependency-name: react-icons
  dependency-version: 5.6.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: prod-minor-updates
- dependency-name: reodotdev
  dependency-version: 1.1.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: prod-minor-updates
- dependency-name: stripe
  dependency-version: 20.4.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: prod-minor-updates
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-10 16:30:13 -07:00
dependabot[bot] 2ba225299e Bump the dev-minor-updates group across 1 directory with 5 updates
Bumps the dev-minor-updates group with 5 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [@dotenvx/dotenvx](https://github.com/dotenvx/dotenvx) | `1.52.0` | `1.53.0` |
| [@tailwindcss/postcss](https://github.com/tailwindlabs/tailwindcss/tree/HEAD/packages/@tailwindcss-postcss) | `4.1.18` | `4.2.1` |
| [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) | `25.2.3` | `25.3.5` |
| [tailwindcss](https://github.com/tailwindlabs/tailwindcss/tree/HEAD/packages/tailwindcss) | `4.1.18` | `4.2.1` |
| [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint) | `8.55.0` | `8.56.1` |



Updates `@dotenvx/dotenvx` from 1.52.0 to 1.53.0
- [Release notes](https://github.com/dotenvx/dotenvx/releases)
- [Changelog](https://github.com/dotenvx/dotenvx/blob/main/CHANGELOG.md)
- [Commits](https://github.com/dotenvx/dotenvx/compare/v1.52.0...v1.53.0)

Updates `@tailwindcss/postcss` from 4.1.18 to 4.2.1
- [Release notes](https://github.com/tailwindlabs/tailwindcss/releases)
- [Changelog](https://github.com/tailwindlabs/tailwindcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/tailwindlabs/tailwindcss/commits/v4.2.1/packages/@tailwindcss-postcss)

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

Updates `tailwindcss` from 4.1.18 to 4.2.1
- [Release notes](https://github.com/tailwindlabs/tailwindcss/releases)
- [Changelog](https://github.com/tailwindlabs/tailwindcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/tailwindlabs/tailwindcss/commits/v4.2.1/packages/tailwindcss)

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

---
updated-dependencies:
- dependency-name: "@dotenvx/dotenvx"
  dependency-version: 1.53.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: dev-minor-updates
- dependency-name: "@tailwindcss/postcss"
  dependency-version: 4.2.1
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: dev-minor-updates
- dependency-name: "@types/node"
  dependency-version: 25.3.5
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: dev-minor-updates
- dependency-name: tailwindcss
  dependency-version: 4.2.1
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: dev-minor-updates
- dependency-name: typescript-eslint
  dependency-version: 8.56.1
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: dev-minor-updates
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-10 16:30:00 -07:00
Owen cc841d5640 Add some logging to debug 2026-03-10 14:24:57 -07:00
Shreyas Papinwar fa0818d3fa fix: ensure Credenza dialog max-height 2026-03-10 10:07:36 -07:00
Owen dec358c4cd Use native drizzle count 2026-03-10 10:03:49 -07:00
Shreyas Papinwar 5455d1c118 fix: add locale to myDevice user query to fix type error 2026-03-10 12:33:05 +05:30
Shreyas Papinwar ae39084a75 fix: persist user locale preference to database (#1547) 2026-03-10 12:21:06 +05:30
Owen e98f873f81 Clean up 2026-03-09 21:16:37 -07:00
Owen e9a2a7e752 Reorder delete 2026-03-09 20:46:27 -07:00
Owen 06015d5191 Handle gerbil rejecting 0
Closes #2605
2026-03-09 17:35:25 -07:00
Owen af688d2a23 Add demo link 2026-03-09 17:35:04 -07:00
Owen 7d0b3ec6b5 Fix not pulling wildcard cert updates 2026-03-09 17:34:48 -07:00
Owen cf5fb8dc33 Working on jit 2026-03-09 16:36:13 -07:00
Rodney Osodo 27d20eb1bc refactor(install): improve resource cleanup and remove unused funcs
Signed-off-by: Rodney Osodo <socials@rodneyosodo.com>
2026-03-09 11:17:36 +03:00
dependabot[bot] 2e2684c695 Bump docker/login-action from 3.7.0 to 4.0.0
Bumps [docker/login-action](https://github.com/docker/login-action) from 3.7.0 to 4.0.0.
- [Release notes](https://github.com/docker/login-action/releases)
- [Commits](https://github.com/docker/login-action/compare/c94ce9fb468520275223c153574b00df6fe4bcc9...b45d80f862d83dbcd57f89517bcf500b2ab88fb2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-09 01:36:08 +00:00
dependabot[bot] 7e2fd8f49d Bump actions/setup-node from 6.2.0 to 6.3.0
Bumps [actions/setup-node](https://github.com/actions/setup-node) from 6.2.0 to 6.3.0.
- [Release notes](https://github.com/actions/setup-node/releases)
- [Commits](https://github.com/actions/setup-node/compare/6044e13b5dc448c55e2357c09f80417699197238...53b83947a5a98c8d113130e565377fae1a50d02f)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-09 01:36:02 +00:00
Owen Schwartz 9a0a255445 Merge pull request #2524 from shreyaspapi/fix/2294-path-based-routing
fix: path-based routing broken due to key collisions in sanitize()
2026-03-07 21:18:59 -08:00
Owen Schwartz 91b7ceb2cf Merge pull request #2603 from Fizza-Mukhtar/fix/prevent-dashboard-domain-conflict-2595
fix: prevent resource from being created with dashboard's domain to avoid redirect loop
2026-03-07 21:15:53 -08:00
Owen Schwartz d5a37436c0 Merge pull request #2616 from LaurenceJJones/fix/issue-240-hcStatus-missing
fix(newt): missing hcStatus in hc config on reconnect
2026-03-07 21:14:27 -08:00
Laurence be609b5000 Fix missing hcStatus field in health check config on reconnect
The buildTargetConfigurationForNewtClient function was not including the
  hcStatus field when building health check targets for the newt/wg/connect
  message. This caused custom expected response codes (e.g., 409) to revert
  to the default 2xx range check after Pangolin server restart.

  Added hcStatus to both the database select query and the returned health
  check target object, matching the behavior in targets.ts addTargets.
2026-03-07 06:28:10 +00:00
Owen 0503c6e66e Handle JIT for ssh 2026-03-06 15:49:17 -08:00
Owen Schwartz d4b830b9bb Merge pull request #2613 from fosrl/dependabot/npm_and_yarn/multi-43b302174d
Bump fast-xml-parser and @aws-sdk/xml-builder
2026-03-06 14:16:27 -08:00
dependabot[bot] 14d6ff25a7 Bump fast-xml-parser and @aws-sdk/xml-builder
Bumps [fast-xml-parser](https://github.com/NaturalIntelligence/fast-xml-parser) and [@aws-sdk/xml-builder](https://github.com/aws/aws-sdk-js-v3/tree/HEAD/packages-internal/xml-builder). These dependencies needed to be updated together.

Updates `fast-xml-parser` from 5.3.6 to 5.4.1
- [Release notes](https://github.com/NaturalIntelligence/fast-xml-parser/releases)
- [Changelog](https://github.com/NaturalIntelligence/fast-xml-parser/blob/master/CHANGELOG.md)
- [Commits](https://github.com/NaturalIntelligence/fast-xml-parser/compare/v5.3.6...v5.4.1)

Updates `@aws-sdk/xml-builder` from 3.972.5 to 3.972.10
- [Release notes](https://github.com/aws/aws-sdk-js-v3/releases)
- [Changelog](https://github.com/aws/aws-sdk-js-v3/blob/main/packages-internal/xml-builder/CHANGELOG.md)
- [Commits](https://github.com/aws/aws-sdk-js-v3/commits/HEAD/packages-internal/xml-builder)

---
updated-dependencies:
- dependency-name: fast-xml-parser
  dependency-version: 5.4.1
  dependency-type: indirect
- dependency-name: "@aws-sdk/xml-builder"
  dependency-version: 3.972.10
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-06 22:14:56 +00:00
Owen Schwartz 1f62f305ce Merge pull request #2611 from fosrl/dependabot/npm_and_yarn/express-rate-limit-8.2.2
Bump express-rate-limit from 8.2.1 to 8.2.2
2026-03-06 14:13:32 -08:00
Owen 9405b0b70a Force jit above site limit 2026-03-06 14:09:57 -08:00
Owen a26ee4ac1a Adjust billing upgrade language 2026-03-06 12:17:26 -08:00
dependabot[bot] cebcf3e337 Bump express-rate-limit from 8.2.1 to 8.2.2
Bumps [express-rate-limit](https://github.com/express-rate-limit/express-rate-limit) from 8.2.1 to 8.2.2.
- [Release notes](https://github.com/express-rate-limit/express-rate-limit/releases)
- [Commits](https://github.com/express-rate-limit/express-rate-limit/compare/v8.2.1...v8.2.2)

---
updated-dependencies:
- dependency-name: express-rate-limit
  dependency-version: 8.2.2
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-06 18:40:41 +00:00
Fizza-Mukhtar 4cfcc64481 fix: use config instead of process.env for dashboard URL check 2026-03-05 01:07:30 -08:00
Fizza-Mukhtar 1a2069a6d9 fix: prevent resource creation with dashboard domain to avoid redirect loop 2026-03-05 00:39:03 -08:00
Owen 2a5c9465e9 Add chainId field passthrough 2026-03-04 22:17:58 -08:00
Owen f36b66e397 Merge branch 'dev' into jit 2026-03-04 17:58:50 -08:00
Owen 8c6d44677d Update lock 2026-03-04 17:48:58 -08:00
Owen 1bfff630bf Jit working for sites 2026-03-04 17:46:58 -08:00
miloschwartz ebcef28b05 remove resend from config 2026-03-04 17:45:48 -08:00
miloschwartz e87e12898c remove resend 2026-03-04 17:45:22 -08:00
miloschwartz d60ab281cf remove resend from package.json 2026-03-04 17:42:25 -08:00
Owen Schwartz 483d54a9f0 Merge pull request #2598 from fosrl/marketing-consetn
add consent boolean to schema
2026-03-04 15:52:08 -08:00
miloschwartz 0ab6ff9148 add consent boolean to schema 2026-03-04 15:50:42 -08:00
Owen c73a39f797 Allow JIT based on site or resource 2026-03-04 15:44:27 -08:00
Owen Schwartz c87b6872e5 Merge pull request #2594 from fosrl/dev
Translations
2026-03-03 21:37:56 -08:00
Owen Schwartz f315c8bc43 Merge pull request #2569 from fosrl/crowdin_dev
New Crowdin updates
2026-03-03 21:34:23 -08:00
Owen Schwartz 20fa1519fd New translations en-us.json (French) 2026-03-03 21:33:01 -08:00
Owen Schwartz 54430afc40 New translations en-us.json (Norwegian Bokmal) 2026-03-03 21:32:59 -08:00
Owen Schwartz 7990d08fee New translations en-us.json (Chinese Simplified) 2026-03-03 21:32:58 -08:00
Owen Schwartz e9042d9e2e New translations en-us.json (Turkish) 2026-03-03 21:32:57 -08:00
Owen Schwartz 24a15841e4 New translations en-us.json (Russian) 2026-03-03 21:32:55 -08:00
Owen Schwartz bb8f6e09fd New translations en-us.json (Portuguese) 2026-03-03 21:32:54 -08:00
Owen Schwartz 04bc8ab694 New translations en-us.json (Polish) 2026-03-03 21:32:52 -08:00
Owen Schwartz 6ac8335cf2 New translations en-us.json (Dutch) 2026-03-03 21:32:51 -08:00
Owen Schwartz 4c6144f8fb New translations en-us.json (Korean) 2026-03-03 21:32:50 -08:00
Owen Schwartz 255003794e New translations en-us.json (Italian) 2026-03-03 21:32:48 -08:00
Owen Schwartz 119d5c79a0 New translations en-us.json (German) 2026-03-03 21:32:47 -08:00
Owen Schwartz 8e2d7c25df New translations en-us.json (Czech) 2026-03-03 21:32:46 -08:00
Owen Schwartz 753dee3023 New translations en-us.json (Bulgarian) 2026-03-03 21:32:44 -08:00
Owen Schwartz cac0272952 New translations en-us.json (Spanish) 2026-03-03 21:32:43 -08:00
Owen Schwartz ee5b74f9fc Merge pull request #2593 from fosrl/dev
1.16.2-s.2
2026-03-03 21:17:10 -08:00
Owen 1362b72cd3 Restrict what can be a header 2026-03-03 21:10:52 -08:00
Owen Schwartz 35b1566962 New translations en-us.json (French) 2026-03-03 20:42:42 -08:00
Owen Schwartz a4bcce5a0c New translations en-us.json (Norwegian Bokmal) 2026-03-03 20:42:40 -08:00
Owen Schwartz c03f1946e8 New translations en-us.json (Chinese Simplified) 2026-03-03 20:42:39 -08:00
Owen Schwartz c11e107758 New translations en-us.json (Turkish) 2026-03-03 20:42:37 -08:00
Owen Schwartz 3b4e49f63a New translations en-us.json (Russian) 2026-03-03 20:42:36 -08:00
Owen Schwartz ea7253f7e8 New translations en-us.json (Portuguese) 2026-03-03 20:42:34 -08:00
Owen Schwartz 8a529f7946 New translations en-us.json (Polish) 2026-03-03 20:42:33 -08:00
Owen Schwartz e76612e018 New translations en-us.json (Dutch) 2026-03-03 20:42:31 -08:00
Owen Schwartz e1f99985d8 New translations en-us.json (Korean) 2026-03-03 20:42:30 -08:00
Owen Schwartz e0c2735635 New translations en-us.json (Italian) 2026-03-03 20:42:28 -08:00
Owen Schwartz 8e6b4e243d New translations en-us.json (German) 2026-03-03 20:42:27 -08:00
Owen Schwartz 2623fa8f02 New translations en-us.json (Czech) 2026-03-03 20:42:25 -08:00
Owen Schwartz 7ff92d32cd New translations en-us.json (Bulgarian) 2026-03-03 20:42:24 -08:00
Owen Schwartz c7f691b20a New translations en-us.json (Spanish) 2026-03-03 20:42:23 -08:00
Owen db042e520e Adjust language 2026-03-03 20:34:56 -08:00
miloschwartz 4cab693cfc openapi and swagger ui improvements and cleanup 2026-03-03 14:54:17 -08:00
Owen c9515ae77c Add comment about not needing exit node 2026-03-03 14:54:17 -08:00
miloschwartz d14de86f65 fix org selector spacing on mobile 2026-03-03 14:54:17 -08:00
Laurence f6ee9db730 enhance(sidebar): make mobile org selector sticky
Make org selector sticky on mobile sidebar

  Move OrgSelector outside the scrollable container so it stays fixed
  at the top while menu items scroll, matching the desktop sidebar
  behavior introduced in 9b2c0d0b.
2026-03-03 14:54:17 -08:00
ChanningHe 94353aea44 feat(integration): add domain CRUD endpoints to integration API 2026-03-03 14:54:17 -08:00
Owen b01fcc70fe Fix ts and add note about ipv4 2026-03-03 14:45:18 -08:00
miloschwartz ed95f10fcc openapi and swagger ui improvements and cleanup 2026-03-02 21:59:41 -08:00
Owen 35fed74e49 Merge branch 'dev' into msg-opt 2026-03-02 18:52:35 -08:00
Owen 64bae5b142 Merge branch 'main' into dev 2026-03-02 18:52:20 -08:00
Owen 6cf1b9b010 Support improved targets msg v2 2026-03-02 18:51:48 -08:00
Owen dae169540b Fix defaults for orgs 2026-03-02 16:49:17 -08:00
Owen 19f9dda490 Add comment about not needing exit node 2026-03-02 16:28:01 -08:00
dependabot[bot] a060c8029f Bump actions/upload-artifact from 6.0.0 to 7.0.0
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 6.0.0 to 7.0.0.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/b7c566a772e6b6bfb58ed0dc250532a479d7789f...bbbca2ddaa5d8feaa63e36b76fdaad77386f024f)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-02 01:36:11 +00:00
dependabot[bot] aca9d1e070 Bump actions/setup-go from 6.2.0 to 6.3.0
Bumps [actions/setup-go](https://github.com/actions/setup-go) from 6.2.0 to 6.3.0.
- [Release notes](https://github.com/actions/setup-go/releases)
- [Commits](https://github.com/actions/setup-go/compare/7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5...4b73464bb391d4059bd26b0524d20df3927bd417)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-02 01:36:05 +00:00
Owen Schwartz cdf79edb00 Merge pull request #2570 from Fizza-Mukhtar/fix/mixed-target-failover-2448
fix: local targets ignored when newt site is unhealthy (mixed target failover)
2026-03-01 15:58:25 -08:00
Owen Schwartz df53dfc936 New translations en-us.json (French) 2026-03-01 11:17:30 -08:00
Owen Schwartz 8e2e09ab81 New translations en-us.json (Norwegian Bokmal) 2026-03-01 11:17:28 -08:00
Owen Schwartz 1eac7cbccd New translations en-us.json (Chinese Simplified) 2026-03-01 11:17:27 -08:00
Owen Schwartz ddaaed65e4 New translations en-us.json (Turkish) 2026-03-01 11:17:26 -08:00
Owen Schwartz 8e633c21c7 New translations en-us.json (Russian) 2026-03-01 11:17:24 -08:00
Owen Schwartz e7c4ef44d8 New translations en-us.json (Portuguese) 2026-03-01 11:17:23 -08:00
Owen Schwartz 3d71470bd2 New translations en-us.json (Polish) 2026-03-01 11:17:21 -08:00
Owen Schwartz dd627a222e New translations en-us.json (Dutch) 2026-03-01 11:17:20 -08:00
Owen Schwartz 62cc20fa1c New translations en-us.json (Korean) 2026-03-01 11:17:19 -08:00
Owen Schwartz 0450fc9f57 New translations en-us.json (Italian) 2026-03-01 11:17:17 -08:00
Owen Schwartz c58aaf5ba6 New translations en-us.json (German) 2026-03-01 11:17:16 -08:00
Owen Schwartz 655522d4e2 New translations en-us.json (Czech) 2026-03-01 11:17:15 -08:00
Owen Schwartz 225475dcae New translations en-us.json (Bulgarian) 2026-03-01 11:17:13 -08:00
Owen Schwartz ccb977fdfb New translations en-us.json (Spanish) 2026-03-01 11:17:12 -08:00
Milo Schwartz 280cbb6e22 Merge pull request #2553 from LaurenceJJones/explore/static-org-dropdown
enhance(sidebar): make mobile org selector sticky
2026-03-01 11:14:16 -08:00
miloschwartz c20babcb53 fix org selector spacing on mobile 2026-03-01 11:13:49 -08:00
Owen Schwartz 768eebe2cd Merge pull request #2432 from ChanningHe/feat-integration-api-domain-crud
feat(integration): add domain CRUD endpoints to integration API
2026-03-01 11:12:05 -08:00
Owen Schwartz 44e3eedffa Merge pull request #2567 from marcschaeferger/fix-kubernetes-install
feat(kubernetes): enable newtInstances by default and update installation instructions
2026-03-01 10:56:18 -08:00
Marc Schäfer bb189874cb fix(newt-install): conditionally display Kubernetes installation info
Signed-off-by: Marc Schäfer <git@marcschaeferger.de>
2026-03-01 10:55:58 -08:00
Marc Schäfer 34dadd0e16 feat(kubernetes): enable newtInstances by default and update installation instructions
Signed-off-by: Marc Schäfer <git@marcschaeferger.de>
2026-03-01 10:55:58 -08:00
Owen Schwartz 87b5cd9988 Merge pull request #2573 from Fizza-Mukhtar/fix/container-search-excludes-labels-2228
fix: exclude labels from container search to prevent false positives
2026-03-01 10:52:50 -08:00
Marc Schäfer 6a537a23e8 fix(newt-install): conditionally display Kubernetes installation info
Signed-off-by: Marc Schäfer <git@marcschaeferger.de>
2026-03-01 18:17:45 +01:00
Fizza-Mukhtar e63a6e9b77 fix: treat local and wireguard sites as online for failover 2026-03-01 07:56:47 -08:00
Fizza-Mukhtar 7ce589c4f2 fix: exclude labels from container search to prevent false positives 2026-03-01 06:50:03 -08:00
Shreyas Papinwar 75a909784a fix: simplify path encoding per review — inline utils, use single key scheme
Address PR review comments:
- Remove pathUtils.ts and move sanitize/encodePath directly into utils.ts
- Simplify dual-key approach to single key using encodePath for map keys
- Remove backward-compat logic (not needed per reviewer)
- Update tests to match simplified approach
2026-03-01 15:48:26 +05:30
Shreyas 244f497a9c test: add comprehensive backward compatibility tests for path routing fix 2026-03-01 15:48:26 +05:30
Shreyas e58f0c9f07 fix: preserve backward-compatible router names while fixing path collisions
Use encodePath only for internal map key grouping (collision-free) and
sanitize for Traefik-facing router/service names (unchanged for existing
users). Extract pure functions into pathUtils.ts so tests can run without
DB dependencies.
2026-03-01 15:48:26 +05:30
Shreyas 5f18c06e03 fix: use collision-free path encoding for Traefik router key generation 2026-03-01 15:48:26 +05:30
Fizza-Mukhtar f36cf06e26 fix: fallback to local targets when newt targets are unhealthy 2026-03-01 01:43:15 -08:00
Owen Schwartz 27d52646a0 New translations en-us.json (Norwegian Bokmal) 2026-02-28 20:13:31 -08:00
Owen Schwartz 4dd8080c55 New translations en-us.json (Chinese Simplified) 2026-02-28 20:13:29 -08:00
Owen Schwartz 0b35d4f2e3 New translations en-us.json (Turkish) 2026-02-28 20:13:28 -08:00
Owen Schwartz 54a9fb9e54 New translations en-us.json (Russian) 2026-02-28 20:13:27 -08:00
Owen Schwartz 60a9e68f02 New translations en-us.json (Portuguese) 2026-02-28 20:13:25 -08:00
Owen Schwartz ad374298e3 New translations en-us.json (Polish) 2026-02-28 20:13:24 -08:00
Owen Schwartz c5dc4e6127 New translations en-us.json (Dutch) 2026-02-28 20:13:22 -08:00
Owen Schwartz 291ad831c5 New translations en-us.json (Korean) 2026-02-28 20:13:21 -08:00
Owen Schwartz 0a018f0ca8 New translations en-us.json (Italian) 2026-02-28 20:13:20 -08:00
Owen Schwartz 6673eeb1bb New translations en-us.json (German) 2026-02-28 20:13:18 -08:00
Owen Schwartz 4641f0b9ef New translations en-us.json (Czech) 2026-02-28 20:13:17 -08:00
Owen Schwartz a4487964e5 New translations en-us.json (Bulgarian) 2026-02-28 20:13:15 -08:00
Owen Schwartz fe42fdd1ec New translations en-us.json (Spanish) 2026-02-28 20:13:14 -08:00
Marc Schäfer 375211f184 feat(kubernetes): enable newtInstances by default and update installation instructions
Signed-off-by: Marc Schäfer <git@marcschaeferger.de>
2026-02-28 23:56:28 +01:00
Owen 66c377a5c9 Merge branch 'main' into dev 2026-02-28 12:14:41 -08:00
Owen 50c2aa0111 Add default memory limits 2026-02-28 12:14:27 -08:00
Owen fdeb891137 Fix pagination effecting drop downs 2026-02-28 12:07:42 -08:00
Owen Schwartz 6a6e3a43b1 Merge pull request #2562 from LaurenceJJones/fix/zod-openapi-catch-error
fix(zod): Add openapi call after catch
2026-02-28 11:04:10 -08:00
Laurence b0a34fa21b fix(openapi): Add openapi call after catch
fix: #2561
without making an explicit call to openapi a runtime error happens because it cannot infer the type, the call to openapi is the same across the codebase
2026-02-28 11:27:19 +00:00
Jacky Fong 5c4de03588 add reset bandwidth api for site
Change endpoint

update to reset all site in the organization

move the logic to organization

move the permission to organization
2026-02-28 15:47:03 +08:00
Owen 72bf6f3c41 Comma seperated 2026-02-27 17:53:44 -08:00
miloschwartz ad9289e0c1 sort by name by default 2026-02-27 15:53:27 -08:00
Owen Schwartz b0cb0e5a99 Merge pull request #2559 from fosrl/dev
1.16.1
2026-02-27 12:40:23 -08:00
miloschwartz 8347203bbe add sort to name col 2026-02-27 12:39:26 -08:00
miloschwartz 4aa1186aed fix machine client pagination 2026-02-27 11:59:55 -08:00
Owen eed87af61d Use ecr base to build 2026-02-26 21:43:14 -08:00
Owen daeea8e7ea Add alises to quieries
Fixes #2556
2026-02-26 21:37:47 -08:00
Owen 0d63a15715 Merge branch 'main' into dev 2026-02-26 20:14:41 -08:00
miloschwartz fa2e229ada support authPath in device login 2026-02-26 14:59:34 -08:00
Laurence 81c1a1da9c enhance(sidebar): make mobile org selector sticky
Make org selector sticky on mobile sidebar

  Move OrgSelector outside the scrollable container so it stays fixed
  at the top while menu items scroll, matching the desktop sidebar
  behavior introduced in 9b2c0d0b.
2026-02-26 15:45:41 +00:00
Owen 5d9700d84c Update sum 2026-02-25 16:46:20 -08:00
Owen f8a8cdaa5f Show enterprise tier 2026-02-25 16:45:35 -08:00
Owen e23e446476 Fix rule violations of dynamic import 2026-02-25 16:35:57 -08:00
Owen fa097df50b Dont maxmind on oss or enterprise 2026-02-25 16:26:33 -08:00
Owen 75f34ff127 Stub cache 2026-02-25 16:17:06 -08:00
Owen c9586b4d93 Remove postgres logs from private config file 2026-02-25 16:09:26 -08:00
Owen 52937a6d90 Make sshCA GA 2026-02-25 16:04:47 -08:00
Owen Schwartz 186c131cce Merge pull request #2543 from fosrl/dev
1.16.0-rc.0
2026-02-25 15:51:42 -08:00
Owen Schwartz 8de3f9a440 Merge pull request #2542 from fosrl/crowdin_dev
New Crowdin updates
2026-02-25 15:49:34 -08:00
Owen Schwartz ea49e179f9 New translations en-us.json (Norwegian Bokmal) 2026-02-25 15:48:16 -08:00
Owen Schwartz 485f4f1c8e New translations en-us.json (Chinese Simplified) 2026-02-25 15:48:15 -08:00
Owen Schwartz 5fb35d12d7 New translations en-us.json (Turkish) 2026-02-25 15:48:13 -08:00
Owen Schwartz ec8a9fe3d2 New translations en-us.json (Russian) 2026-02-25 15:48:12 -08:00
Owen Schwartz 411a34e15e New translations en-us.json (Portuguese) 2026-02-25 15:48:11 -08:00
Owen Schwartz 3df71fd2bc New translations en-us.json (Polish) 2026-02-25 15:48:09 -08:00
Owen Schwartz 5e1f6085e3 New translations en-us.json (Dutch) 2026-02-25 15:48:08 -08:00
Owen Schwartz 53fc7ab6e3 New translations en-us.json (Korean) 2026-02-25 15:48:07 -08:00
Owen Schwartz 7779ed24fe New translations en-us.json (Italian) 2026-02-25 15:48:05 -08:00
Owen Schwartz 6e4193dae3 New translations en-us.json (German) 2026-02-25 15:48:04 -08:00
Owen Schwartz f138609f48 New translations en-us.json (Czech) 2026-02-25 15:48:02 -08:00
Owen Schwartz 98154b5de3 New translations en-us.json (Bulgarian) 2026-02-25 15:48:01 -08:00
Owen Schwartz 6322fd9eef New translations en-us.json (Spanish) 2026-02-25 15:47:59 -08:00
Owen Schwartz 1c0949e957 New translations en-us.json (French) 2026-02-25 15:47:58 -08:00
ChanningHe 52f26396ac feat(integration): add domain CRUD endpoints to integration API 2026-02-26 08:44:55 +09:00
Owen c3847e6001 Prefix usernames 2026-02-25 15:36:22 -08:00
Owen 5cf13a963d Add missing saving username 2026-02-25 15:30:34 -08:00
miloschwartz b017877826 hide ssh access tab for cidr resources 2026-02-25 14:49:28 -08:00
Owen 959f68b520 Restrict cidr resource 2026-02-25 14:43:47 -08:00
Owen 14cab3fdb8 Update phrase 2026-02-25 14:37:52 -08:00
Owen b8d468f6de Bump version 2026-02-25 14:22:24 -08:00
Owen fc66394243 Merge branch 'main' into dev 2026-02-25 14:18:22 -08:00
miloschwartz 8fca243c9a update tierMatrix 2026-02-25 11:59:16 -08:00
miloschwartz 388f710379 add pg migration 2026-02-25 11:37:31 -08:00
Owen Schwartz ba3ab4362b Merge pull request #2539 from fosrl/dependabot/npm_and_yarn/prod-minor-updates-22e7e52815
Bump the prod-minor-updates group across 1 directory with 3 updates
2026-02-25 11:32:19 -08:00
miloschwartz e18c9afc2d add sqlite migration 2026-02-25 11:24:32 -08:00
Owen Schwartz a9b4a86c4a Merge pull request #2470 from fosrl/dependabot/npm_and_yarn/qs-6.14.2
Bump qs from 6.14.1 to 6.14.2
2026-02-25 11:05:46 -08:00
Owen Schwartz 200ea502dd Merge pull request #2484 from fosrl/dependabot/go_modules/install/minor-updates-80eb8af454
Bump golang.org/x/term from 0.39.0 to 0.40.0 in /install in the minor-updates group
2026-02-25 11:05:34 -08:00
dependabot[bot] de36db97eb Bump the prod-minor-updates group across 1 directory with 3 updates
Bumps the prod-minor-updates group with 3 updates in the / directory: [pg](https://github.com/brianc/node-postgres/tree/HEAD/packages/pg), [posthog-node](https://github.com/PostHog/posthog-js/tree/HEAD/packages/node) and [tailwind-merge](https://github.com/dcastil/tailwind-merge).


Updates `pg` from 8.18.0 to 8.19.0
- [Changelog](https://github.com/brianc/node-postgres/blob/master/CHANGELOG.md)
- [Commits](https://github.com/brianc/node-postgres/commits/pg@8.19.0/packages/pg)

Updates `posthog-node` from 5.24.15 to 5.26.0
- [Release notes](https://github.com/PostHog/posthog-js/releases)
- [Changelog](https://github.com/PostHog/posthog-js/blob/main/packages/node/CHANGELOG.md)
- [Commits](https://github.com/PostHog/posthog-js/commits/posthog-node@5.26.0/packages/node)

Updates `tailwind-merge` from 3.4.0 to 3.5.0
- [Release notes](https://github.com/dcastil/tailwind-merge/releases)
- [Commits](https://github.com/dcastil/tailwind-merge/compare/v3.4.0...v3.5.0)

---
updated-dependencies:
- dependency-name: pg
  dependency-version: 8.19.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: prod-minor-updates
- dependency-name: posthog-node
  dependency-version: 5.26.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: prod-minor-updates
- dependency-name: tailwind-merge
  dependency-version: 3.5.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: prod-minor-updates
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-25 19:03:51 +00:00
dependabot[bot] 30283b044f Bump golang.org/x/term in /install in the minor-updates group
Bumps the minor-updates group in /install with 1 update: [golang.org/x/term](https://github.com/golang/term).


Updates `golang.org/x/term` from 0.39.0 to 0.40.0
- [Commits](https://github.com/golang/term/compare/v0.39.0...v0.40.0)

---
updated-dependencies:
- dependency-name: golang.org/x/term
  dependency-version: 0.40.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: minor-updates
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-25 19:01:31 +00:00
Owen Schwartz 055bed8a07 Merge pull request #2338 from fosrl/dependabot/github_actions/actions/checkout-6.0.2
Bump actions/checkout from 6.0.1 to 6.0.2
2026-02-25 11:00:55 -08:00
Owen Schwartz 12b5c2ab34 Merge pull request #2495 from fosrl/dependabot/npm_and_yarn/dev-patch-updates-ebb414287f
Bump @types/nodemailer from 7.0.9 to 7.0.10 in the dev-patch-updates group across 1 directory
2026-02-25 11:00:24 -08:00
Owen Schwartz dd78674888 Merge pull request #2514 from fosrl/dependabot/npm_and_yarn/multi-60f2582fdd
Bump fast-xml-parser and @aws-sdk/xml-builder
2026-02-25 11:00:15 -08:00
Owen Schwartz 0d0df63847 Merge pull request #2518 from fosrl/dependabot/github_actions/actions/stale-10.2.0
Bump actions/stale from 10.1.1 to 10.2.0
2026-02-25 11:00:01 -08:00
Owen Schwartz 3ab00d9da8 Merge pull request #2527 from fosrl/dependabot/npm_and_yarn/prod-patch-updates-cbac17d765
Bump the prod-patch-updates group across 1 directory with 7 updates
2026-02-25 10:59:38 -08:00
Owen Schwartz 3e6e72c5c7 Merge pull request #2531 from fosrl/dependabot/npm_and_yarn/multi-40a89e2d0a
Bump minimatch
2026-02-25 10:59:08 -08:00
Owen Schwartz 5d8a55f08c Merge pull request #2415 from fosrl/dependabot/github_actions/aws-actions/configure-aws-credentials-6
Bump aws-actions/configure-aws-credentials from 5 to 6
2026-02-25 10:58:18 -08:00
Owen Schwartz 81c569aae4 Merge pull request #2393 from fosrl/dependabot/github_actions/docker/login-action-3.7.0
Bump docker/login-action from 3.6.0 to 3.7.0
2026-02-25 10:58:11 -08:00
Owen Schwartz 88fd3fc4da Merge pull request #2296 from fosrl/dependabot/npm_and_yarn/lodash-4.17.23
Bump lodash from 4.17.21 to 4.17.23
2026-02-25 10:58:02 -08:00
Owen 2282d3ae39 Fix formatting 2026-02-25 10:53:56 -08:00
Owen c4dcec463a Merge branch 'dev' into LaurenceJJones-feature/installer-tui 2026-02-25 10:48:05 -08:00
Owen 5b7f893ad7 Merge branch 'main' into dev 2026-02-25 10:46:28 -08:00
Owen 2ede0d498a remove log 2026-02-25 10:46:20 -08:00
Owen f518e8a0ff Merge branch 'feature/installer-tui' of github.com:LaurenceJJones/pangolin into LaurenceJJones-feature/installer-tui 2026-02-25 10:45:38 -08:00
Owen Schwartz 767284408a Merge pull request #2499 from LaurenceJJones/feature/build-variables
enhance(installer): use ldflags to inject versions
2026-02-25 10:42:04 -08:00
Owen Schwartz eef51f3b84 Merge pull request #2491 from rodneyosodo/fix/install
fix(install): add error handling, code cleanups, and YAML type refactor
2026-02-25 10:41:03 -08:00
Owen Schwartz 69b7114a49 Merge pull request #2537 from Abhinav-kodes/fix/toggle-hydration-sync
fix: sync resource toggle states with context on initial load
2026-02-25 10:36:58 -08:00
Owen Schwartz 0ea38ea568 Merge pull request #2535 from Abhinav-kodes/fix-resource-session-delete-cookie
fix: correct session DELETE tautology and HTTP cookie domain interpolation
2026-02-25 10:35:09 -08:00
Abhinav-kodes c600da71e3 fix: sync resource toggle states with context on initial load
- Replace defaultChecked with checked for controlled components
- Add useEffect to sync rulesEnabled, ssoEnabled, whitelistEnabled
  when resource context hydrates after mount
- Add nullish coalescing fallback to prevent undefined initial state
2026-02-25 22:07:08 +05:30
Abhinav-kodes c64dd14b1a fix: correct session DELETE tautology and HTTP cookie domain interpolation 2026-02-25 17:24:27 +05:30
miloschwartz 8ea6d9fa67 add get user by username search endpoint to integration api 2026-02-24 22:04:15 -08:00
Owen 978ac8f53c Add logging 2026-02-24 20:51:27 -08:00
Owen 49a326cde7 Add trust proxy to the internal api
Fix access logs not having the right ip
2026-02-24 20:23:42 -08:00
Owen 63e208f4ec Use local cache in verify session 2026-02-24 19:56:16 -08:00
Owen f50d1549b0 Update cache to use redis 2026-02-24 19:50:42 -08:00
Owen 55e24df671 Check and prefer user token if provided 2026-02-24 19:48:32 -08:00
Owen b37e1d0cc0 Use debian slim; alpine broken? 2026-02-24 19:48:16 -08:00
Owen afa26c0dd4 Exclude migrations? 2026-02-24 19:48:08 -08:00
miloschwartz c71f46ede5 move copy button and fix translation 2026-02-24 19:44:08 -08:00
miloschwartz 20e547a0f6 first pass 2026-02-24 17:58:11 -08:00
dependabot[bot] 2edebaddc2 Bump the prod-patch-updates group across 1 directory with 7 updates
Bumps the prod-patch-updates group with 7 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [@asteasolutions/zod-to-openapi](https://github.com/asteasolutions/zod-to-openapi) | `8.4.0` | `8.4.1` |
| [@react-email/components](https://github.com/resend/react-email/tree/HEAD/packages/components) | `1.0.7` | `1.0.8` |
| [@react-email/tailwind](https://github.com/resend/react-email/tree/HEAD/packages/tailwind) | `2.0.4` | `2.0.5` |
| [@simplewebauthn/server](https://github.com/MasterKale/SimpleWebAuthn/tree/HEAD/packages/server) | `13.2.2` | `13.2.3` |
| [glob](https://github.com/isaacs/node-glob) | `13.0.3` | `13.0.6` |
| [next-intl](https://github.com/amannn/next-intl) | `4.8.2` | `4.8.3` |
| [react-hook-form](https://github.com/react-hook-form/react-hook-form) | `7.71.1` | `7.71.2` |



Updates `@asteasolutions/zod-to-openapi` from 8.4.0 to 8.4.1
- [Release notes](https://github.com/asteasolutions/zod-to-openapi/releases)
- [Commits](https://github.com/asteasolutions/zod-to-openapi/compare/v8.4.0...v8.4.1)

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

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

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

Updates `glob` from 13.0.3 to 13.0.6
- [Changelog](https://github.com/isaacs/node-glob/blob/main/changelog.md)
- [Commits](https://github.com/isaacs/node-glob/compare/v13.0.3...v13.0.6)

Updates `next-intl` from 4.8.2 to 4.8.3
- [Release notes](https://github.com/amannn/next-intl/releases)
- [Changelog](https://github.com/amannn/next-intl/blob/main/CHANGELOG.md)
- [Commits](https://github.com/amannn/next-intl/compare/v4.8.2...v4.8.3)

Updates `react-hook-form` from 7.71.1 to 7.71.2
- [Release notes](https://github.com/react-hook-form/react-hook-form/releases)
- [Changelog](https://github.com/react-hook-form/react-hook-form/blob/master/CHANGELOG.md)
- [Commits](https://github.com/react-hook-form/react-hook-form/compare/v7.71.1...v7.71.2)

---
updated-dependencies:
- dependency-name: "@asteasolutions/zod-to-openapi"
  dependency-version: 8.4.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: prod-patch-updates
- dependency-name: "@react-email/components"
  dependency-version: 1.0.8
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: prod-patch-updates
- dependency-name: "@react-email/tailwind"
  dependency-version: 2.0.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: prod-patch-updates
- dependency-name: "@simplewebauthn/server"
  dependency-version: 13.2.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: prod-patch-updates
- dependency-name: glob
  dependency-version: 13.0.6
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: prod-patch-updates
- dependency-name: next-intl
  dependency-version: 4.8.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: prod-patch-updates
- dependency-name: react-hook-form
  dependency-version: 7.71.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: prod-patch-updates
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-25 01:37:31 +00:00
dependabot[bot] 119e1d4867 Bump @types/nodemailer in the dev-patch-updates group across 1 directory
Bumps the dev-patch-updates group with 1 update in the / directory: [@types/nodemailer](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/nodemailer).


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

---
updated-dependencies:
- dependency-name: "@types/nodemailer"
  dependency-version: 7.0.10
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-patch-updates
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-25 01:35:31 +00:00
dependabot[bot] 63e30d3378 Bump minimatch
Bumps  and [minimatch](https://github.com/isaacs/minimatch). These dependencies needed to be updated together.

Updates `minimatch` from 10.2.0 to 10.2.3
- [Changelog](https://github.com/isaacs/minimatch/blob/main/changelog.md)
- [Commits](https://github.com/isaacs/minimatch/compare/v10.2.0...v10.2.3)

Updates `minimatch` from 3.1.2 to 3.1.4
- [Changelog](https://github.com/isaacs/minimatch/blob/main/changelog.md)
- [Commits](https://github.com/isaacs/minimatch/compare/v10.2.0...v10.2.3)

Updates `minimatch` from 9.0.5 to 9.0.7
- [Changelog](https://github.com/isaacs/minimatch/blob/main/changelog.md)
- [Commits](https://github.com/isaacs/minimatch/compare/v10.2.0...v10.2.3)

Updates `minimatch` from 10.1.1 to 10.2.3
- [Changelog](https://github.com/isaacs/minimatch/blob/main/changelog.md)
- [Commits](https://github.com/isaacs/minimatch/compare/v10.2.0...v10.2.3)

---
updated-dependencies:
- dependency-name: minimatch
  dependency-version: 10.2.3
  dependency-type: indirect
- dependency-name: minimatch
  dependency-version: 3.1.4
  dependency-type: indirect
- dependency-name: minimatch
  dependency-version: 9.0.7
  dependency-type: indirect
- dependency-name: minimatch
  dependency-version: 10.2.3
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-25 01:04:10 +00:00
Owen d6fe04ec4e Fix orgid issue when regen credentials 2026-02-24 14:26:10 -08:00
Owen b8a364af6a Fix log query 2026-02-23 22:01:11 -08:00
Owen 5ef808d4a2 Merge branch 'main' into logs-database 2026-02-23 16:39:39 -08:00
miloschwartz 848d4d91e6 fix sidebar 2026-02-23 13:40:08 -08:00
Owen a502780c9b Fix sso username issue 2026-02-22 22:05:02 -08:00
Owen Schwartz 418e099804 Merge pull request #2521 from fosrl/dev
1.15.4-s.6
2026-02-22 21:13:51 -08:00
Owen Schwartz 06258aa386 Merge pull request #2513 from fosrl/crowdin_dev
New Crowdin updates
2026-02-22 21:13:17 -08:00
Owen Schwartz d7608b1cc8 New translations en-us.json (Norwegian Bokmal) 2026-02-22 21:11:37 -08:00
Owen Schwartz cb86ad4104 New translations en-us.json (Chinese Simplified) 2026-02-22 21:11:35 -08:00
Owen Schwartz 8cd51df1e1 New translations en-us.json (Turkish) 2026-02-22 21:11:34 -08:00
Owen Schwartz 8ef7220766 New translations en-us.json (Russian) 2026-02-22 21:11:33 -08:00
Owen Schwartz b5333a3686 New translations en-us.json (Portuguese) 2026-02-22 21:11:31 -08:00
Owen Schwartz e6e92dbc0f New translations en-us.json (Polish) 2026-02-22 21:11:30 -08:00
Owen Schwartz 01fdd41a10 New translations en-us.json (Dutch) 2026-02-22 21:11:28 -08:00
Owen Schwartz 6af06a38ae New translations en-us.json (Korean) 2026-02-22 21:11:27 -08:00
Owen Schwartz 5d9c66d22d New translations en-us.json (Italian) 2026-02-22 21:11:26 -08:00
Owen Schwartz 81f5a4b127 New translations en-us.json (German) 2026-02-22 21:11:24 -08:00
Owen Schwartz da3e68a20b New translations en-us.json (Czech) 2026-02-22 21:11:23 -08:00
Owen Schwartz 8712c1719e New translations en-us.json (Bulgarian) 2026-02-22 21:11:22 -08:00
Owen Schwartz 593c5db0e8 New translations en-us.json (Spanish) 2026-02-22 21:11:20 -08:00
Owen Schwartz b28391feae New translations en-us.json (French) 2026-02-22 21:11:19 -08:00
Owen 5f8df6d4cd Merge branch 'main' into dev 2026-02-22 21:02:58 -08:00
Owen c36efe7f14 Add translations 2026-02-22 21:02:21 -08:00
Owen cf97b6df9c Handle billing bad subs, remove exit node name from lock, prevent some
stuff on saas
2026-02-22 20:45:53 -08:00
dependabot[bot] 720d3a8135 Bump actions/stale from 10.1.1 to 10.2.0
Bumps [actions/stale](https://github.com/actions/stale) from 10.1.1 to 10.2.0.
- [Release notes](https://github.com/actions/stale/releases)
- [Changelog](https://github.com/actions/stale/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/stale/compare/997185467fa4f803885201cee163a9f38240193d...b5d41d4e1d5dceea10e7104786b73624c18a190f)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-23 01:36:30 +00:00
dependabot[bot] 9c42458fa5 Bump actions/checkout from 6.0.1 to 6.0.2
Bumps [actions/checkout](https://github.com/actions/checkout) from 6.0.1 to 6.0.2.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/8e8c483db84b4bee98b60c0593521ed34d9990e8...de0fac2e4500dabe0009e67214ff5f5447ce83dd)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: 6.0.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-23 01:31:48 +00:00
Owen 6d9b129ac9 Merge branch 'main' into k8s 2026-02-22 17:28:09 -08:00
miloschwartz e17ec798d4 remove local fonts 2026-02-22 11:18:57 -08:00
miloschwartz 58ac499f30 add safeRead 2026-02-21 16:38:51 -08:00
miloschwartz f07f0092ad testing with local font 2026-02-21 14:34:38 -08:00
dependabot[bot] bcd3475d17 Bump fast-xml-parser and @aws-sdk/xml-builder
Bumps [fast-xml-parser](https://github.com/NaturalIntelligence/fast-xml-parser) and [@aws-sdk/xml-builder](https://github.com/aws/aws-sdk-js-v3/tree/HEAD/packages-internal/xml-builder). These dependencies needed to be updated together.

Updates `fast-xml-parser` from 5.3.4 to 5.3.6
- [Release notes](https://github.com/NaturalIntelligence/fast-xml-parser/releases)
- [Changelog](https://github.com/NaturalIntelligence/fast-xml-parser/blob/master/CHANGELOG.md)
- [Commits](https://github.com/NaturalIntelligence/fast-xml-parser/compare/v5.3.4...v5.3.6)

Updates `@aws-sdk/xml-builder` from 3.972.4 to 3.972.5
- [Release notes](https://github.com/aws/aws-sdk-js-v3/releases)
- [Changelog](https://github.com/aws/aws-sdk-js-v3/blob/main/packages-internal/xml-builder/CHANGELOG.md)
- [Commits](https://github.com/aws/aws-sdk-js-v3/commits/HEAD/packages-internal/xml-builder)

---
updated-dependencies:
- dependency-name: fast-xml-parser
  dependency-version: 5.3.6
  dependency-type: indirect
- dependency-name: "@aws-sdk/xml-builder"
  dependency-version: 3.972.5
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-21 20:18:36 +00:00
Owen Schwartz 7c04526088 New translations en-us.json (Czech) 2026-02-21 05:24:27 -08:00
Owen Schwartz 2d7ab68576 New translations en-us.json (Czech) 2026-02-21 04:14:18 -08:00
miloschwartz 218a4893b6 hide address on sites and clients 2026-02-20 22:47:56 -08:00
miloschwartz 266bf261aa update note in migration 2026-02-20 22:45:37 -08:00
Owen Schwartz 63694032e8 Merge pull request #2511 from fosrl/dev
1.15.4-s.4
2026-02-20 20:45:23 -08:00
Owen Schwartz b77aaedb58 Merge pull request #2510 from fosrl/crowdin_dev
New Crowdin updates
2026-02-20 20:44:45 -08:00
Owen Schwartz a316d0301f New translations en-us.json (Norwegian Bokmal) 2026-02-20 20:43:45 -08:00
Owen Schwartz dcd499720e New translations en-us.json (Chinese Simplified) 2026-02-20 20:43:44 -08:00
Owen Schwartz e18fe21eca New translations en-us.json (Turkish) 2026-02-20 20:43:43 -08:00
Owen Schwartz 2970b51fb8 New translations en-us.json (Russian) 2026-02-20 20:43:42 -08:00
Owen Schwartz b9236ff52e New translations en-us.json (Portuguese) 2026-02-20 20:43:41 -08:00
Owen Schwartz 38eb0ec7ed New translations en-us.json (Polish) 2026-02-20 20:43:39 -08:00
Owen Schwartz ecba4a0b80 New translations en-us.json (Dutch) 2026-02-20 20:43:38 -08:00
Owen Schwartz e6da18c952 New translations en-us.json (Korean) 2026-02-20 20:43:37 -08:00
Owen Schwartz 12941ac5ae New translations en-us.json (Italian) 2026-02-20 20:43:36 -08:00
Owen Schwartz 11085bda63 New translations en-us.json (German) 2026-02-20 20:43:35 -08:00
Owen Schwartz c03211cc53 New translations en-us.json (Czech) 2026-02-20 20:43:33 -08:00
Owen Schwartz 2867459600 New translations en-us.json (Bulgarian) 2026-02-20 20:43:32 -08:00
Owen Schwartz 32b24db9bf New translations en-us.json (Spanish) 2026-02-20 20:43:31 -08:00
Owen Schwartz 660bf9ff87 New translations en-us.json (French) 2026-02-20 20:43:30 -08:00
miloschwartz 78c4ddebba set defaults 2026-02-20 20:35:57 -08:00
miloschwartz f2dfadb37b show disabled button for owner 2026-02-20 20:28:47 -08:00
miloschwartz 3f2bdf081f allow edit admin role ssh and support null authDaemonMode 2026-02-20 20:25:14 -08:00
miloschwartz d6ba34aeea set auth daemon type on resource 2026-02-20 17:33:21 -08:00
Owen b622aca221 Try to route logs requests to a different database 2026-02-20 17:20:01 -08:00
miloschwartz 6442eb12fb more visual adjustments 2026-02-20 10:43:25 -08:00
miloschwartz 01c15afa74 other visual adjustments 2026-02-19 23:41:04 -08:00
miloschwartz 4e88f1f38a more sidebar improvements 2026-02-19 22:41:14 -08:00
miloschwartz 13ab505f4d add ease to sidebar menu 2026-02-19 21:59:49 -08:00
miloschwartz 7d112aab27 improve alignment on sidebar 2026-02-19 21:52:47 -08:00
Owen b786497299 Working on k8s 2026-02-19 17:55:49 -08:00
miloschwartz eedf57af89 disable rybbit in saas 2026-02-19 17:54:40 -08:00
miloschwartz 7a01a4e090 ssh settings on a role 2026-02-19 17:53:11 -08:00
Owen 874794c996 Clean email 2026-02-18 14:07:50 -08:00
Owen 5e37c4e85f Resolve potential issues with processing roleIds 2026-02-18 13:55:04 -08:00
Owen 4e7eac368f Uniform ne check on niceId and dont reject clients 2026-02-18 11:56:01 -08:00
Laurence e8398cb221 enhance(installer): use huh package to handle input
Instead of relying on stdin and stdout by default, using the huh package from charmbracelet allows us to handle user input more gracefully such as y/n instead of typing 'yes' or 'no'. If a user makes a mistake whilst typing in any text fields they cannot use left or right to edit a single character when using huh it can. This adds a dependancy and may increase the size of installer but overall improves user experience.
2026-02-18 11:05:48 +00:00
Laurence 9460e28c7b ehance(installer): use ldflags to inject versions
Instead of the CI/CD using sed to replace the 'replaceme' text we can instead use ldflags which can inject variables at build time to the versions. The makefile had a bunch of workarounds for dev so these have been removed to cleanup etc etc and fetchs versions from the gh api directly if the variables are not injected like the CI/CD does
2026-02-18 09:43:41 +00:00
Owen 756f3f32ca Merge branch 'dev' 2026-02-17 21:57:49 -08:00
Owen Schwartz 362981ad19 Merge pull request #2498 from fosrl/crowdin_dev
New Crowdin updates
2026-02-17 21:57:14 -08:00
Owen Schwartz fa4f7e4ac2 New translations en-us.json (Norwegian Bokmal) 2026-02-17 21:56:24 -08:00
Owen Schwartz c6bca4e2ab New translations en-us.json (Chinese Simplified) 2026-02-17 21:56:23 -08:00
Owen Schwartz e28b361e05 New translations en-us.json (Turkish) 2026-02-17 21:56:22 -08:00
Owen Schwartz a18691011b New translations en-us.json (Russian) 2026-02-17 21:56:20 -08:00
Owen Schwartz c4a6403cba New translations en-us.json (Portuguese) 2026-02-17 21:56:19 -08:00
Owen Schwartz 1851bf941a New translations en-us.json (Polish) 2026-02-17 21:56:18 -08:00
Owen Schwartz b7ab3c2e92 New translations en-us.json (Dutch) 2026-02-17 21:56:16 -08:00
Owen Schwartz ce1ad032ba New translations en-us.json (Korean) 2026-02-17 21:56:15 -08:00
Owen Schwartz 8446c68e1b New translations en-us.json (Italian) 2026-02-17 21:56:14 -08:00
Owen Schwartz 40ed388b0f New translations en-us.json (German) 2026-02-17 21:56:12 -08:00
Owen Schwartz ce1693aa2f New translations en-us.json (Czech) 2026-02-17 21:56:11 -08:00
Owen Schwartz 11d16a1552 New translations en-us.json (Bulgarian) 2026-02-17 21:56:10 -08:00
Owen Schwartz 0ac54a2c88 New translations en-us.json (Spanish) 2026-02-17 21:56:08 -08:00
Owen Schwartz b7d8b32123 New translations en-us.json (French) 2026-02-17 21:56:07 -08:00
Owen 5987f6b2cd Allow enterprise 2026-02-17 21:55:57 -08:00
miloschwartz 7ad76f5683 allow type hyphen in orgID 2026-02-17 21:54:55 -08:00
Owen 09a9457021 Fix transaction issue 2026-02-17 21:27:23 -08:00
dependabot[bot] d8b45396e3 Bump qs from 6.14.1 to 6.14.2
Bumps [qs](https://github.com/ljharb/qs) from 6.14.1 to 6.14.2.
- [Changelog](https://github.com/ljharb/qs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/ljharb/qs/compare/v6.14.1...v6.14.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-18 05:08:23 +00:00
Owen Schwartz ca4643ec36 Merge pull request #2494 from fosrl/dev
1.15.4-s.3
2026-02-17 21:07:04 -08:00
Owen e2f78ba476 Merge branch 'main' of github.com:fosrl/pangolin into dev 2026-02-17 21:06:16 -08:00
Owen 5d92190d50 Merge branch 'cloud-multi-org' into dev 2026-02-17 21:01:44 -08:00
Owen 2b0d6de986 Handle feature lifecycle for multiple orgs 2026-02-17 21:00:48 -08:00
Owen 057f82a561 Fix some cosmetics 2026-02-17 20:46:02 -08:00
Owen 719d2a5ffe Count everything when deleting the org 2026-02-17 20:39:47 -08:00
miloschwartz d4bff9d5cb clean orgId and fix primary badge 2026-02-17 20:35:36 -08:00
Owen 19fcc1f93b Set org limit 2026-02-17 20:18:50 -08:00
miloschwartz d45ea127c2 use billing org id in get subscription status 2026-02-17 20:07:29 -08:00
Owen f591cf8601 Look to the right org to test is subscribed 2026-02-17 20:06:58 -08:00
Owen 6661a76aa8 Update member resources page and testing new org counts 2026-02-17 20:01:43 -08:00
miloschwartz a2ed22bfcc use add/remove helper functions in auto (de)provision 2026-02-17 17:50:41 -08:00
Owen e370f8891a Also update in the assign 2026-02-17 17:34:57 -08:00
miloschwartz 8a83e32c42 add send email verification opt out 2026-02-17 17:33:35 -08:00
Owen 831eb6325c Centralize user functions 2026-02-17 17:31:41 -08:00
Owen 4d6240c987 Handle new usage tracking with multi org 2026-02-17 17:10:05 -08:00
miloschwartz 79cf7c84dc support delete org and preserve path on switch 2026-02-17 16:45:15 -08:00
Owen b71f582329 Use the billing org id when updating and checking usage 2026-02-17 15:09:42 -08:00
Owen 8315d4b6ae Dont create ca certs quite yet 2026-02-17 14:48:13 -08:00
miloschwartz b8c3cc751a support creating multiple orgs in saas 2026-02-17 14:37:46 -08:00
Owen d00262dc31 Send the right port and cert 2026-02-17 11:43:38 -08:00
Rodney Osodo 952d0c74d0 refactor(install): use any for YAML map types instead of interface{}
Signed-off-by: Rodney Osodo <socials@rodneyosodo.com>
2026-02-17 10:45:06 +03:00
Rodney Osodo ffbea7af59 fix(install): add error handling and minor code cleanups
Signed-off-by: Rodney Osodo <socials@rodneyosodo.com>
2026-02-17 10:45:06 +03:00
Owen 3debc6c8d3 Add round trip tracking for any message 2026-02-16 20:29:55 -08:00
Owen 5092eb58fb Ssh host should be the destination 2026-02-16 15:31:09 -08:00
Owen f0b9240575 Accept resource as either niceId or alias 2026-02-16 15:29:23 -08:00
Owen 9cf59c409e Initial sign endpoint working 2026-02-16 15:19:29 -08:00
dependabot[bot] 971c375398 Bump docker/login-action from 3.6.0 to 3.7.0
Bumps [docker/login-action](https://github.com/docker/login-action) from 3.6.0 to 3.7.0.
- [Release notes](https://github.com/docker/login-action/releases)
- [Commits](https://github.com/docker/login-action/compare/5e57cd118135c172c3672efd75eb46360885c0ef...c94ce9fb468520275223c153574b00df6fe4bcc9)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-15 19:12:16 +00:00
dependabot[bot] ac4439c5ae Bump aws-actions/configure-aws-credentials from 5 to 6
Bumps [aws-actions/configure-aws-credentials](https://github.com/aws-actions/configure-aws-credentials) from 5 to 6.
- [Release notes](https://github.com/aws-actions/configure-aws-credentials/releases)
- [Changelog](https://github.com/aws-actions/configure-aws-credentials/blob/main/CHANGELOG.md)
- [Commits](https://github.com/aws-actions/configure-aws-credentials/compare/v5...v6)

---
updated-dependencies:
- dependency-name: aws-actions/configure-aws-credentials
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-15 19:12:05 +00:00
Owen bfd5aa30a7 Merge branch 'dev' of github.com:fosrl/pangolin into dev 2026-02-15 11:09:11 -08:00
Owen 9737170665 Merge branch 'Lokowitz-update-packages' into dev 2026-02-15 11:08:12 -08:00
Owen 922a040466 Merge branch 'update-packages' of github.com:Lokowitz/pangolin into Lokowitz-update-packages 2026-02-15 11:08:02 -08:00
miloschwartz 9eacefb155 support delete account 2026-02-14 22:44:30 -08:00
miloschwartz 33f0782f3a support delete account 2026-02-14 18:01:37 -08:00
Milo Schwartz e6a5cef945 Merge pull request #2371 from Fredkiss3/refactor/paginated-tables
feat: server side filtered, ordered & paginated tables
2026-02-14 11:43:01 -08:00
miloschwartz 4c8edb80b3 dont show table footer in client-side data-table 2026-02-14 11:40:59 -08:00
miloschwartz d4668fae99 add openapi types 2026-02-14 11:25:00 -08:00
Fred KISSIE ddfe55e3ae ♻️ add niceId to query filtering on most tables 2026-02-14 04:19:30 +01:00
Fred KISSIE 761a5f1d4c ♻️ use like & LOWER(column) for searching with query 2026-02-14 04:11:27 +01:00
Fred KISSIE 1fbcad8787 ♻️ refactor 2026-02-14 04:06:11 +01:00
miloschwartz aba586e605 change translation 2026-02-13 17:35:54 -08:00
Milo Schwartz 27b21b5ad4 Merge pull request #2359 from Fredkiss3/feat/logo-path-in-enterprise
feat: Support file path in branding logo URL for enterprise
2026-02-13 17:16:33 -08:00
Milo Schwartz b6e54dab17 Merge branch 'dev' into feat/logo-path-in-enterprise 2026-02-13 17:16:25 -08:00
miloschwartz 1f8e89772d disable global idp routes if idp mode is org 2026-02-13 15:46:13 -08:00
Owen 843b13ed57 Try to fix cicd 2026-02-13 15:00:17 -08:00
Owen be89e5ca55 Fix issue with auto provisioning being overriden 2026-02-13 14:56:56 -08:00
Lokowitz 5f3657fd56 update packages 2026-02-13 06:15:26 +00:00
Lokowitz 494162400e Merge remote-tracking branch 'origin/dev' into update-packages 2026-02-13 06:12:24 +00:00
Fred KISSIE ab65bb6a8a Merge branch 'dev' into refactor/paginated-tables 2026-02-13 06:03:09 +01:00
miloschwartz 333625f199 rename starter in cloud to basic 2026-02-12 20:24:23 -08:00
Owen dbfd715381 Fix windows formatting 2026-02-12 16:27:51 -08:00
Owen Schwartz f1d989964e Merge pull request #2471 from fosrl/dev
Update translations
2026-02-12 16:14:41 -08:00
Owen Schwartz b701629498 Merge pull request #2465 from fosrl/crowdin_dev
New Crowdin updates
2026-02-12 16:14:17 -08:00
Owen Schwartz 8250946325 New translations en-us.json (Norwegian Bokmal) 2026-02-12 16:14:04 -08:00
Owen Schwartz 71f63d8e6f New translations en-us.json (Chinese Simplified) 2026-02-12 16:14:03 -08:00
Owen Schwartz dd5e834db0 New translations en-us.json (Turkish) 2026-02-12 16:14:01 -08:00
Owen Schwartz 970ecb52f0 New translations en-us.json (Russian) 2026-02-12 16:14:00 -08:00
Owen Schwartz 62ea1b40e1 New translations en-us.json (Portuguese) 2026-02-12 16:13:58 -08:00
Owen Schwartz 3b0fd5c592 New translations en-us.json (Polish) 2026-02-12 16:13:57 -08:00
Owen Schwartz b7616026dd New translations en-us.json (Dutch) 2026-02-12 16:13:55 -08:00
Owen Schwartz 16ad60b89a New translations en-us.json (Korean) 2026-02-12 16:13:54 -08:00
Owen Schwartz db7971d2f7 New translations en-us.json (Italian) 2026-02-12 16:13:53 -08:00
Owen Schwartz f3f8bd3125 New translations en-us.json (German) 2026-02-12 16:13:51 -08:00
Owen Schwartz 516fd0ee8f New translations en-us.json (Czech) 2026-02-12 16:13:50 -08:00
Owen Schwartz 8d6700d493 New translations en-us.json (Bulgarian) 2026-02-12 16:13:49 -08:00
Owen Schwartz 9d4ace9b3e New translations en-us.json (Spanish) 2026-02-12 16:13:47 -08:00
Owen Schwartz 2800655e33 New translations en-us.json (French) 2026-02-12 16:13:45 -08:00
Owen Schwartz 91eecee11d Merge pull request #2469 from fosrl/dev
1.15.4
2026-02-12 16:10:44 -08:00
Owen Schwartz 899e5aa395 New translations en-us.json (Norwegian Bokmal) 2026-02-12 15:41:25 -08:00
Owen Schwartz d5820c4902 New translations en-us.json (Chinese Simplified) 2026-02-12 15:41:24 -08:00
Owen Schwartz a91c002274 New translations en-us.json (Turkish) 2026-02-12 15:41:22 -08:00
Owen Schwartz 4d142b93dd New translations en-us.json (Russian) 2026-02-12 15:41:21 -08:00
Owen Schwartz 04dcf57ff3 New translations en-us.json (Portuguese) 2026-02-12 15:41:20 -08:00
Owen Schwartz 975550c755 New translations en-us.json (Polish) 2026-02-12 15:41:18 -08:00
Owen Schwartz a964a80d85 New translations en-us.json (Dutch) 2026-02-12 15:41:17 -08:00
Owen Schwartz 22c3b8f116 New translations en-us.json (Korean) 2026-02-12 15:41:15 -08:00
Owen Schwartz c4b1831cfe New translations en-us.json (Italian) 2026-02-12 15:41:14 -08:00
Owen Schwartz cdb6813384 New translations en-us.json (German) 2026-02-12 15:41:13 -08:00
Owen Schwartz b14b68d83c New translations en-us.json (Czech) 2026-02-12 15:41:11 -08:00
Owen Schwartz 3c2f930e6b New translations en-us.json (Bulgarian) 2026-02-12 15:41:10 -08:00
Owen Schwartz ca9c7ce555 New translations en-us.json (Spanish) 2026-02-12 15:41:08 -08:00
Owen Schwartz c2e95a0607 New translations en-us.json (French) 2026-02-12 15:41:06 -08:00
miloschwartz 2767ee9e80 update pangolin cli links 2026-02-12 15:29:08 -08:00
miloschwartz d998a8087f fix pg migration 2026-02-12 15:06:24 -08:00
miloschwartz fdce016921 add 1.15.4 migration 2026-02-12 15:00:12 -08:00
miloschwartz c73d70933b bump version 2026-02-12 14:52:29 -08:00
miloschwartz e9d0ad6e37 use pangolin cli for container 2026-02-12 14:51:25 -08:00
Owen a35586f762 Add sudo 2026-02-12 14:47:55 -08:00
miloschwartz f527c30923 add post auth url 2026-02-12 14:21:50 -08:00
Owen 94e70219cf Make install sudo because run is sudo 2026-02-12 14:12:45 -08:00
Owen 6496763aae Cap retention days 2026-02-12 12:18:42 -08:00
Owen a409ec269b Change back to lokowitz db method 2026-02-12 12:13:13 -08:00
Owen bc7bc8da66 Stop tracking files that should be ignored 2026-02-12 12:07:57 -08:00
Owen 52484c774e Setting up drizzle and fix site not showing in private resource 2026-02-12 12:05:15 -08:00
Lokowitz 4e1e0cade1 upgrade package 2026-02-12 15:51:19 +00:00
Lokowitz fda5904dac Merge remote-tracking branch 'origin/dev' into update-packages 2026-02-12 15:47:29 +00:00
Owen Schwartz 69ecc22318 New translations en-us.json (German) 2026-02-12 01:39:38 -08:00
Owen bff9d33ee6 Move back to db:sqlite:generate 2026-02-11 21:47:10 -08:00
Owen Schwartz edf506953b Merge pull request #2463 from fosrl/dev
1.15.3
2026-02-11 21:42:04 -08:00
Owen Schwartz 5e11746549 New translations en-us.json (Norwegian Bokmal) 2026-02-11 21:40:26 -08:00
Owen Schwartz 1ae315e303 New translations en-us.json (Chinese Simplified) 2026-02-11 21:40:26 -08:00
Owen Schwartz 758b03ab25 New translations en-us.json (Turkish) 2026-02-11 21:40:26 -08:00
Owen Schwartz e756fad573 New translations en-us.json (Russian) 2026-02-11 21:40:26 -08:00
Owen Schwartz 3547450b03 New translations en-us.json (Portuguese) 2026-02-11 21:40:26 -08:00
Owen Schwartz 733f6692c6 New translations en-us.json (Polish) 2026-02-11 21:40:26 -08:00
Owen Schwartz 2d83160b16 New translations en-us.json (Dutch) 2026-02-11 21:40:26 -08:00
Owen Schwartz 256fa880dd New translations en-us.json (Korean) 2026-02-11 21:40:26 -08:00
Owen Schwartz b08c5f5c67 New translations en-us.json (Italian) 2026-02-11 21:40:26 -08:00
Owen Schwartz d0862a2d26 New translations en-us.json (German) 2026-02-11 21:40:26 -08:00
Owen Schwartz e97340ed52 New translations en-us.json (Czech) 2026-02-11 21:40:26 -08:00
Owen Schwartz e27c81eea6 New translations en-us.json (Bulgarian) 2026-02-11 21:40:26 -08:00
Owen Schwartz 7f7f3d43b2 New translations en-us.json (Spanish) 2026-02-11 21:40:26 -08:00
Owen Schwartz 4b1b772098 New translations en-us.json (French) 2026-02-11 21:40:26 -08:00
Owen Schwartz f66b88490f New translations en-us.json (Norwegian Bokmal) 2026-02-11 21:40:26 -08:00
Owen Schwartz 18f9157169 New translations en-us.json (Chinese Simplified) 2026-02-11 21:40:26 -08:00
Owen Schwartz 6eb82a807b New translations en-us.json (Turkish) 2026-02-11 21:40:26 -08:00
Owen Schwartz bf57a97833 New translations en-us.json (Russian) 2026-02-11 21:40:26 -08:00
Owen Schwartz e9e2093220 New translations en-us.json (Portuguese) 2026-02-11 21:40:26 -08:00
Owen Schwartz c3540da2e3 New translations en-us.json (Polish) 2026-02-11 21:40:26 -08:00
Owen Schwartz d228cf56dd New translations en-us.json (Dutch) 2026-02-11 21:40:26 -08:00
Owen Schwartz 8f4cecd963 New translations en-us.json (Korean) 2026-02-11 21:40:26 -08:00
Owen Schwartz 66adff44bb New translations en-us.json (Italian) 2026-02-11 21:40:26 -08:00
Owen Schwartz be41c094dc New translations en-us.json (German) 2026-02-11 21:40:26 -08:00
Owen Schwartz 273848ca18 New translations en-us.json (Czech) 2026-02-11 21:40:26 -08:00
Owen Schwartz 1e9dbead3b New translations en-us.json (Bulgarian) 2026-02-11 21:40:26 -08:00
Owen Schwartz aeaa8ba133 New translations en-us.json (Spanish) 2026-02-11 21:40:26 -08:00
Owen Schwartz 24654af635 New translations en-us.json (French) 2026-02-11 21:40:26 -08:00
Owen Schwartz e88a21d6db New translations en-us.json (Norwegian Bokmal) 2026-02-11 21:40:26 -08:00
Owen Schwartz bcd01badaf New translations en-us.json (Chinese Simplified) 2026-02-11 21:40:26 -08:00
Owen Schwartz 8e063506e0 New translations en-us.json (Turkish) 2026-02-11 21:40:26 -08:00
Owen Schwartz 84f5d6137a New translations en-us.json (Russian) 2026-02-11 21:40:26 -08:00
Owen Schwartz 0a8565f5e8 New translations en-us.json (Portuguese) 2026-02-11 21:40:26 -08:00
Owen Schwartz bd8da25a46 New translations en-us.json (Polish) 2026-02-11 21:40:26 -08:00
Owen Schwartz a841f588dd New translations en-us.json (Dutch) 2026-02-11 21:40:26 -08:00
Owen Schwartz 75a4362ce3 New translations en-us.json (Korean) 2026-02-11 21:40:26 -08:00
Owen Schwartz e763e001e5 New translations en-us.json (Italian) 2026-02-11 21:40:26 -08:00
Owen Schwartz 69475a0ae7 New translations en-us.json (German) 2026-02-11 21:40:26 -08:00
Owen Schwartz 53e14c2ad7 New translations en-us.json (Czech) 2026-02-11 21:40:26 -08:00
Owen Schwartz 1edc33148a New translations en-us.json (Bulgarian) 2026-02-11 21:40:26 -08:00
Owen Schwartz a4cbfc74e4 New translations en-us.json (Spanish) 2026-02-11 21:40:26 -08:00
Owen Schwartz c0d25aeb02 New translations en-us.json (French) 2026-02-11 21:40:26 -08:00
miloschwartz 40f49bf6da add pangolin cloud link 2026-02-11 19:46:19 -08:00
Owen 0bfce87dc6 Ignore migrations 2026-02-11 19:25:00 -08:00
Owen 2a0655e9de Bump version 2026-02-11 19:08:11 -08:00
Owen a86cfa5934 Add missing col in migration 2026-02-11 19:03:23 -08:00
miloschwartz 54b77523c5 remove console.log 2026-02-11 19:02:22 -08:00
Owen ba06c8928d Dont restrict numbers outside of the cloud 2026-02-11 19:01:47 -08:00
miloschwartz c8a4ac1ed4 add global/org idp banner 2026-02-11 19:01:06 -08:00
miloschwartz 143acbae48 add identity provider mode setting 2026-02-11 18:05:06 -08:00
Owen 937f6fdae8 Add better error message 2026-02-11 17:56:58 -08:00
Owen ba7239ac08 Verify everything we can 2026-02-11 17:30:21 -08:00
Owen 2e748274c0 Merge branch 'main' into dev 2026-02-11 17:22:19 -08:00
Owen eab2750953 Add migrations for 1.15.3 2026-02-11 17:21:15 -08:00
miloschwartz 17b6cb0c73 Merge branch 'new-pricing' into dev 2026-02-11 17:11:21 -08:00
Owen 98a4c453c1 Fix anouther subscribed logic issue 2026-02-11 12:41:22 -08:00
Owen 6475dceab9 Rename tiers in features and fix subscribed logic issue 2026-02-11 12:38:18 -08:00
Owen 040a945774 Fix dynamic -> private import violations 2026-02-11 10:30:25 -08:00
Owen 47743a5fa8 Fix private import -> dynamic 2026-02-11 10:26:50 -08:00
Owen Schwartz d47d6de985 New translations en-us.json (Norwegian Bokmal) 2026-02-11 10:23:00 -08:00
Owen Schwartz 37818b8594 New translations en-us.json (Chinese Simplified) 2026-02-11 10:23:00 -08:00
Owen Schwartz 3b184acddd New translations en-us.json (Turkish) 2026-02-11 10:23:00 -08:00
Owen Schwartz 9c80404d17 New translations en-us.json (Russian) 2026-02-11 10:23:00 -08:00
Owen Schwartz aaa7082f9d New translations en-us.json (Portuguese) 2026-02-11 10:23:00 -08:00
Owen Schwartz a45b45b2ce New translations en-us.json (Polish) 2026-02-11 10:23:00 -08:00
Owen Schwartz e4bfbd267e New translations en-us.json (Dutch) 2026-02-11 10:23:00 -08:00
Owen Schwartz 65b4dcc672 New translations en-us.json (Korean) 2026-02-11 10:23:00 -08:00
Owen Schwartz 36fc30b524 New translations en-us.json (Italian) 2026-02-11 10:23:00 -08:00
Owen Schwartz e724ed9137 New translations en-us.json (German) 2026-02-11 10:23:00 -08:00
Owen Schwartz 7ca992af05 New translations en-us.json (Czech) 2026-02-11 10:23:00 -08:00
Owen Schwartz 37f1c714ac New translations en-us.json (Bulgarian) 2026-02-11 10:23:00 -08:00
Owen Schwartz 397a43fb60 New translations en-us.json (Spanish) 2026-02-11 10:23:00 -08:00
Owen Schwartz 45e0a648c6 New translations en-us.json (French) 2026-02-11 10:23:00 -08:00
Owen 7336aa81d9 Update database build in dockerfile 2026-02-11 10:15:07 -08:00
miloschwartz d727c10d98 link to billing page 2026-02-11 10:06:57 -08:00
Owen 321d77a317 Fix setting limits 2026-02-11 10:06:57 -08:00
miloschwartz 19b8a6b737 show required tier in paid features alert 2026-02-11 10:06:56 -08:00
Owen f2e69dfb96 Add override for limits 2026-02-11 10:06:56 -08:00
miloschwartz 8207e49317 use purple banner for all paid features alert 2026-02-11 10:06:56 -08:00
miloschwartz b75600b9ea add subscription violation banner 2026-02-11 10:06:56 -08:00
Owen 7b01f1bef6 Remove s3 bucket 2026-02-11 10:06:56 -08:00
Owen e7bd2c0001 Allow cupons 2026-02-11 10:06:56 -08:00
Owen a26076e9db Add final prices and fix logs 2026-02-11 10:06:56 -08:00
Owen 9711a0fb8e Continue to clean things up 2026-02-11 10:06:56 -08:00
Owen accc670411 Communication improvements 2026-02-11 10:06:56 -08:00
Owen 071c41a54f Show warnings and specifics when downgrading 2026-02-11 10:06:56 -08:00
Owen 35ba6c19c3 Disable features when downgrading 2026-02-11 10:06:56 -08:00
miloschwartz 14c8348166 set default org mapping on create global idp 2026-02-11 10:06:56 -08:00
Owen 7d6ee72025 Finish adding limits checks to all put and post 2026-02-11 10:06:56 -08:00
Owen ea0e770b57 Fix error response 2026-02-11 10:06:56 -08:00
Owen 193b7ff21e Adding limit checks 2026-02-11 10:06:55 -08:00
miloschwartz d814ad9f3e add tier matrix to branding page 2026-02-11 10:06:55 -08:00
miloschwartz da8b620c75 refactor and add tiers 2026-02-11 10:06:55 -08:00
miloschwartz 911b5e6814 refactor front end hooks 2026-02-11 10:06:55 -08:00
Owen f991fd9c71 Fix to use the limits file 2026-02-11 10:06:55 -08:00
Owen 652e4c922d Comment out stripe usage reporting 2026-02-11 10:06:55 -08:00
miloschwartz 4364e3fbc1 fix some errors 2026-02-11 10:06:55 -08:00
Owen a783fdecbc Fix errors 2026-02-11 10:06:55 -08:00
Owen 16f67455a2 Handle auto provisioning 2026-02-11 10:06:55 -08:00
Owen 0850a28d20 Add more tier matrix checks 2026-02-11 10:06:55 -08:00
miloschwartz 5ca598139e use pricing matrix in existing usePaidStatus funcitons 2026-02-11 10:06:55 -08:00
Owen df1bf09163 Add pricing matrix 2026-02-11 10:06:55 -08:00
miloschwartz 50bc8d3e9c add rest of tier types 2026-02-11 10:06:55 -08:00
miloschwartz 86d089024e add tier type 2026-02-11 10:06:55 -08:00
Owen d5c1cf594d Remove site kick 2026-02-11 10:06:54 -08:00
Owen a0b5731e69 Getting swtiching tiers to work 2026-02-11 10:06:54 -08:00
miloschwartz ceb359d614 refactor is licensed and subscribed util functions 2026-02-11 10:06:54 -08:00
miloschwartz a49a9f8e3b dont fingerprint machine clients 2026-02-11 10:06:54 -08:00
miloschwartz 766606b08d use pangolin cli in machine client commands 2026-02-11 10:06:54 -08:00
miloschwartz fed56c1959 show features in ce 2026-02-11 10:06:54 -08:00
miloschwartz ae6ed8ad97 prefill username in login 2026-02-11 10:06:54 -08:00
Owen c1ca0b8e2c Set version when creating sub 2026-02-11 10:06:54 -08:00
Owen 569dc735ce Rename tiers and get working 2026-02-11 10:06:54 -08:00
Owen dd11c2c871 Dont accept invite if over the limits 2026-02-11 10:06:54 -08:00
Owen 8def4a2b68 Dont log to stripe 2026-02-11 10:06:54 -08:00
Owen 13a5f24b07 Dont write stripe to files anymore 2026-02-11 10:06:52 -08:00
Owen 0989d6353e Further billing 2026-02-11 10:06:03 -08:00
Owen 4139a7b73f Basic billing page is working 2026-02-11 10:06:03 -08:00
Owen be60d66ce3 Switch to the new tier system and clean up checks 2026-02-11 10:06:03 -08:00
Owen 0a33043874 Switching to new pricing - remove old feature tracking 2026-02-11 10:06:03 -08:00
Owen 96d1d983e5 Wrap insert in transaction
Ref #2222
2026-02-11 10:06:03 -08:00
Owen 7ffb260d7c Change features, remove site uptime 2026-02-11 10:05:53 -08:00
miloschwartz ce74489df5 link to billing page 2026-02-10 22:07:52 -08:00
Owen 342b188fae Fix setting limits 2026-02-10 21:54:26 -08:00
miloschwartz fa6fee7b55 show required tier in paid features alert 2026-02-10 21:33:05 -08:00
Owen c53d5a4d7d Add override for limits 2026-02-10 21:29:19 -08:00
miloschwartz 521e905724 use purple banner for all paid features alert 2026-02-10 21:21:20 -08:00
miloschwartz 4623090050 add subscription violation banner 2026-02-10 21:19:14 -08:00
Owen dd9e5cc541 Remove s3 bucket 2026-02-10 21:14:14 -08:00
Owen 626be6a347 Allow cupons 2026-02-10 21:08:11 -08:00
Owen 56327ed503 Add final prices and fix logs 2026-02-10 20:42:34 -08:00
Fred KISSIE 6d1665004b 🏷️ fix type errors 2026-02-11 04:34:53 +01:00
Fred KISSIE 59b8119fbd Merge branch 'dev' into refactor/paginated-tables 2026-02-11 04:12:40 +01:00
Owen 9ff863db5e Continue to clean things up 2026-02-10 18:30:01 -08:00
Owen e2ac6e6d4d Communication improvements 2026-02-10 17:04:22 -08:00
Owen df4101875a Show warnings and specifics when downgrading 2026-02-10 16:35:09 -08:00
Owen 3f5c788d48 Disable features when downgrading 2026-02-10 16:11:19 -08:00
Fred KISSIE 45cd4df6e5 ♻️ agent 2026-02-11 00:37:42 +01:00
miloschwartz 94ac3ec76e set default org mapping on create global idp 2026-02-10 10:58:01 -08:00
Owen af7263a0b1 Finish adding limits checks to all put and post 2026-02-10 10:53:02 -08:00
Owen 035396f95c Fix error response 2026-02-10 10:53:02 -08:00
Owen f318f6304b Adding limit checks 2026-02-10 10:53:02 -08:00
miloschwartz 9d0ff472e5 add tier matrix to branding page 2026-02-10 10:42:39 -08:00
miloschwartz d27482e812 refactor and add tiers 2026-02-10 10:27:10 -08:00
Lokowitz d5b6de70da update package and update dockerfile 2026-02-10 11:49:53 +00:00
miloschwartz 69c2212ea0 refactor front end hooks 2026-02-09 20:50:44 -08:00
Owen 10be9bcd56 Fix to use the limits file 2026-02-09 20:39:26 -08:00
Owen f531def0d2 Comment out stripe usage reporting 2026-02-09 20:30:44 -08:00
miloschwartz ed40eae655 fix some errors 2026-02-09 20:23:55 -08:00
Owen ba5ae6ed04 Fix errors 2026-02-09 20:17:14 -08:00
Fred KISSIE d6ade102dc filter & paginate on machine clients table 2026-02-10 05:14:37 +01:00
Owen 0a6301697e Handle auto provisioning 2026-02-09 20:11:24 -08:00
Owen 13b4fc6725 Add more tier matrix checks 2026-02-09 19:52:44 -08:00
Fred KISSIE c94d246c24 ♻️ list machine query 2026-02-10 04:00:45 +01:00
Fred KISSIE 5b779ba9fe ♻️ refactor 2026-02-10 03:21:12 +01:00
Fred KISSIE 3ba2cb19a9 approval feed 2026-02-10 03:20:49 +01:00
miloschwartz a095dddd01 use pricing matrix in existing usePaidStatus funcitons 2026-02-09 18:17:18 -08:00
Owen 1b5cfaa49b Add pricing matrix 2026-02-09 18:04:37 -08:00
miloschwartz 66f3fabbae add rest of tier types 2026-02-09 17:52:28 -08:00
miloschwartz 0be8fb7931 add tier type 2026-02-09 17:42:45 -08:00
Owen 431e6ffaae Remove site kick 2026-02-09 17:23:48 -08:00
Owen 7d8185e0ee Getting swtiching tiers to work 2026-02-09 17:05:14 -08:00
miloschwartz dff45748bd refactor is licensed and subscribed util functions 2026-02-09 16:57:41 -08:00
Fred KISSIE da514ef314 ♻️ refactor 2026-02-10 00:45:34 +01:00
Fred KISSIE 7f73cde794 ♻️ refetch approval count every 30s 2026-02-10 00:45:20 +01:00
Fred KISSIE b0af0d9cd5 ♻️ keep previous data 2026-02-10 00:31:21 +01:00
miloschwartz e6464929ff Merge branch 'dev' into new-pricing 2026-02-09 15:05:13 -08:00
miloschwartz 122053939d dont fingerprint machine clients 2026-02-09 14:41:40 -08:00
Lokowitz 8429197b07 fix .gitignore 2026-02-09 19:56:32 +00:00
Lokowitz 44f2081882 update packages 2026-02-09 17:08:59 +00:00
Owen 300b4a3706 Set version when creating sub 2026-02-08 17:56:50 -08:00
Owen 81ef2db7f8 Rename tiers and get working 2026-02-08 17:56:36 -08:00
Owen c41e8be3e8 Dont accept invite if over the limits 2026-02-08 11:55:24 -08:00
Owen 41bab0ce0b Dont log to stripe 2026-02-08 11:13:09 -08:00
Owen 5f26b9eeea Merge branch 'k8s' into new-pricing 2026-02-08 11:08:51 -08:00
Owen 1cca69ad23 Further billing 2026-02-08 11:08:23 -08:00
miloschwartz 410ed3949b use pangolin cli in machine client commands 2026-02-07 17:13:55 -08:00
miloschwartz efc6ef3075 show features in ce 2026-02-07 17:00:44 -08:00
Lokowitz 63f7dd1d20 fix lint error 2026-02-07 08:48:58 +00:00
Lokowitz 57b8c69983 remove date-fns 2026-02-07 08:34:56 +00:00
Lokowitz aad060810a update package and move eslint to dev 2026-02-07 08:26:05 +00:00
Lokowitz 9222b00a6f Merge remote-tracking branch 'origin/dev' into update-packages 2026-02-07 08:14:16 +00:00
Fred KISSIE ff61b22e7e ♻️do not set default values 2026-02-07 05:37:52 +01:00
Fred KISSIE 577cb91343 whole table filter 2026-02-07 05:37:01 +01:00
Fred KISSIE 1889386f64 🚧 wip: table filters 2026-02-07 04:51:37 +01:00
Fred KISSIE 5d7f082ebf sort user device table & refactor sort into common functino 2026-02-07 04:41:42 +01:00
Fred KISSIE db6327c4ff 🔇 remove console.logs in API 2026-02-07 02:52:23 +01:00
Fred KISSIE fd7f6b2b99 filter user devices API finished 2026-02-07 02:51:32 +01:00
Fred KISSIE 49435398a8 🔥 cleanup imports 2026-02-07 02:50:59 +01:00
Owen e101ac341b Basic billing page is working 2026-02-06 17:41:20 -08:00
Owen 6cfc7b7c69 Switch to the new tier system and clean up checks 2026-02-06 16:27:31 -08:00
Owen 313acabc86 Wrap insert in transaction
Ref #2222
2026-02-06 10:48:18 -08:00
Owen 34cced872f Switching to new pricing - remove old feature tracking 2026-02-06 10:47:43 -08:00
Owen ac09e3aaf9 Wrap insert in transaction
Ref #2222
2026-02-06 10:47:19 -08:00
Fred KISSIE 9f2fd34e99 🚧 wip: user devices endpoint 2026-02-06 05:37:44 +01:00
Fred KISSIE 67b63d3084 ♻️ make code cleanrer 2026-02-06 04:52:21 +01:00
Fred KISSIE 4a31a7b84b 🚨 fix lint error 2026-02-06 03:55:11 +01:00
Fred KISSIE 538b601b1e Merge branch 'dev' into refactor/paginated-tables 2026-02-06 03:54:50 +01:00
Fred KISSIE 588f064c25 🚸 make resource enabled switch optimistic 2026-02-06 03:53:14 +01:00
Fred KISSIE d521e79662 🏷️ fix types 2026-02-06 03:21:00 +01:00
Fred KISSIE ccddb9244d 🏷️ add types on mode in sqlite 2026-02-06 03:14:03 +01:00
Fred KISSIE 0547396213 ♻️ do not sort client resources 2026-02-06 02:44:23 +01:00
Fred KISSIE 6c85171091 serverside filter+paginate client resources table 2026-02-06 02:42:15 +01:00
miloschwartz a8f6b6c1da prefill username in login 2026-02-05 16:55:00 -08:00
Owen f899326189 Change features, remove site uptime 2026-02-05 14:56:07 -08:00
Lokowitz 0f4d1d2a74 add preview server 2026-02-05 19:46:57 +00:00
Lokowitz 941d5c08e3 upgrade packages 2026-02-05 19:26:36 +00:00
Lokowitz db9f74158b Merge remote-tracking branch 'origin/dev' into update-packages 2026-02-05 19:24:12 +00:00
Owen b4c01349d1 Merge branch 'dev' 2026-02-04 21:44:07 -08:00
Owen 165bbd3584 Merge branch 'self-serve' into dev 2026-02-04 21:42:32 -08:00
Lokowitz ffb253e0e9 fix dockerfile 2026-02-04 21:42:10 -08:00
Owen e5e9fe456f Add note about the self serve 2026-02-04 21:37:16 -08:00
miloschwartz c63589b204 auto open checkout modal 2026-02-04 21:31:46 -08:00
miloschwartz 11408c2656 add internal redirect 2026-02-04 21:16:59 -08:00
Owen 7d4aed8819 Add prod price ids 2026-02-04 20:37:25 -08:00
Fred KISSIE 609ffccd67 🏷️ fix typescript error 2026-02-05 05:35:59 +01:00
miloschwartz 508369a59d adjust language in form 2026-02-04 20:25:20 -08:00
Fred KISSIE 748af1d8cb ♻️ cleanup code for searching & filtering 2026-02-05 05:21:25 +01:00
Owen 26a91cd5e1 Add link 2026-02-04 18:29:35 -08:00
Owen 48dd4d5913 Billing licenses working 2026-02-04 18:15:46 -08:00
Fred KISSIE d309ec249e filter resources by status 2026-02-05 03:15:18 +01:00
Owen 72d46b7352 Moving to supporting more than one sub 2026-02-04 17:54:29 -08:00
Owen 4613aae47d Handle license lifecycle 2026-02-04 17:37:31 -08:00
Owen 1bc4480d84 Working on complete auth flow 2026-02-04 16:32:53 -08:00
miloschwartz b5d76f73e8 add new tier select form 2026-02-04 16:08:47 -08:00
Owen a5c7913e77 Checkout flow works 2026-02-04 15:49:49 -08:00
miloschwartz 34b914f509 add license email 2026-02-04 15:38:02 -08:00
miloschwartz 5a3d75ca12 add quantity check 2026-02-04 15:19:58 -08:00
Owen 158d7b23d8 Add test button to launch stripe 2026-02-04 14:13:25 -08:00
Fred KISSIE 67949b4968 🚧 wip: healthStatus 2026-02-04 04:10:08 +01:00
Fred KISSIE 1fc40b3017 filter by auth state 2026-02-04 03:42:05 +01:00
Fred KISSIE bb1a375484 paginate, search & filter resources by enabled 2026-02-04 02:20:28 +01:00
Owen bf5dd3b0a1 Pull secrets from env vars 2026-02-02 21:39:18 -08:00
Owen e4d4c62833 Dont create newt sites with exit node or subnet 2026-02-02 18:19:13 -08:00
Owen 20ae903d7f Subscribed limits for domains is higher 2026-02-02 16:46:48 -08:00
Owen f5f757e4bd Subscribed limits for domains is higher 2026-02-02 16:45:54 -08:00
Lokowitz 13c011895d update packages and node 2026-02-02 19:17:40 +00:00
Lokowitz bd8d0e3392 update packages 2026-02-02 18:48:35 +00:00
Owen 5ad564d21b Use rand 2026-02-02 10:25:14 -08:00
miloschwartz 8f8775cb93 override device name with computed device name on register 2026-02-01 17:37:18 -08:00
miloschwartz 37695827aa show user display name on device page 2026-02-01 17:30:05 -08:00
miloschwartz 7a72d209ea add --network host to newt install command for docker run 2026-02-01 17:24:16 -08:00
Fred KISSIE cda6b67bef search, filter & paginate sites table 2026-01-31 03:02:39 +01:00
Fred KISSIE 066305b095 toggle column sorting & pagination 2026-01-31 00:45:14 +01:00
Owen f2ba4b270f Dont write stripe to files anymore 2026-01-29 20:56:46 -08:00
Fred KISSIE 89695df012 🚧 wip: pagination and search work 2026-01-30 05:39:01 +01:00
MoweME b0566d3c6f fix(i18n): correct German site terminology
Updates the German translation to use "Standort" (site) instead of "Seite" (page) for consistency with the site context.
2026-01-29 10:01:30 -08:00
MoweME 5dda8c384f fix(i18n): correct German translation strings
Corrects mistranslation of device timestamp labels and fixes product name reference in site tunnel settings.
2026-01-29 10:01:30 -08:00
Fred KISSIE b04385a340 🚧 search on table 2026-01-29 05:48:41 +01:00
Fred KISSIE d374ea6ea6 🚧wip 2026-01-29 05:07:41 +01:00
Fred KISSIE 01a2820390 🚧 POC: pagination in sites table 2026-01-29 05:07:27 +01:00
Fred KISSIE c89c1a03da 🎨 use prettier for formatting typescript 2026-01-29 05:05:34 +01:00
Lokowitz 873408270e removed unused gomod code 2026-01-28 15:06:23 -08:00
Lokowitz 8fec8f35bc removed unused code 2026-01-28 15:06:23 -08:00
Owen 141c846fe2 Properly insert PANGOLIN_SETUP_TOKEN into db
Fixes #2361
2026-01-28 15:04:17 -08:00
Owen cb569ff14d Properly insert PANGOLIN_SETUP_TOKEN into db
Fixes #2361
2026-01-28 15:03:31 -08:00
Lokowitz 1497469016 revert format:write 2026-01-28 14:50:42 -08:00
Lokowitz e356a6d33b fix lable error and make dockerfile readable 2026-01-28 14:50:42 -08:00
Fred KISSIE 38ac4c5980 🚧 wip: paginated tables 2026-01-28 04:46:54 +01:00
Fred KISSIE ed3ee64e4b support pathname in logo URL in branding page 2026-01-28 03:04:12 +01:00
miloschwartz 12aea2901d fix depreated zod warning 2026-01-26 14:11:03 -08:00
miloschwartz 5ff56467ea error response improvements to logo url 2026-01-26 14:00:22 -08:00
miloschwartz 3a8718a4b0 remove archive confirmtion on account devices dialog 2026-01-26 13:36:25 -08:00
dependabot[bot] 8c15855fc3 Bump lodash from 4.17.21 to 4.17.23
Bumps [lodash](https://github.com/lodash/lodash) from 4.17.21 to 4.17.23.
- [Release notes](https://github.com/lodash/lodash/releases)
- [Commits](https://github.com/lodash/lodash/compare/4.17.21...4.17.23)

---
updated-dependencies:
- dependency-name: lodash
  dependency-version: 4.17.23
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-26 01:37:38 +00:00
Owen 37c4a7b690 Retry verify 2026-01-24 11:55:32 -08:00
Owen b735e7c34d Fix #2314 2026-01-24 11:47:17 -08:00
Owen 5f85c3b3b8 Remove extra rebuild command 2026-01-24 11:35:45 -08:00
miloschwartz 5d9cb9fa21 fix clear olmId from client on archive 2026-01-24 11:11:25 -08:00
miloschwartz 643d56958d fix saas private import 2026-01-23 10:07:05 -08:00
miloschwartz f378d6f040 fix input border 2026-01-22 21:24:28 -08:00
Milo Schwartz bb57794388 Merge pull request #2306 from Fredkiss3/fix/tab-from-host-port
fix: tab between host & port in resource target address column
2026-01-22 21:14:20 -08:00
miloschwartz a9ca49b8a2 Merge branch 'main' into dev 2026-01-22 21:10:40 -08:00
Fred KISSIE c1b473294e 🔥 remove useless useEffect 2026-01-23 04:54:24 +01:00
Fred KISSIE e3e4bdfe09 🚸 fix target item tabbing by memoizing the getColumns (and its dependencies) 2026-01-23 04:40:19 +01:00
miloschwartz bfbeace2e2 fix import in list approvals 2026-01-22 17:54:53 -08:00
miloschwartz efcf46ce8a fix policy check on olm register 2026-01-22 16:28:15 -08:00
miloschwartz 2085715965 fix wrong redirect url when idp login with custom auth domain 2026-01-22 15:46:48 -08:00
Owen d227db7b7b Show the source in the UI 2026-01-22 15:18:27 -08:00
Owen 2af67ad355 Fix the source of the cli blueprint 2026-01-22 15:18:27 -08:00
miloschwartz f100854423 add ios and android to readme 2026-01-22 15:18:27 -08:00
miloschwartz 92331d7a33 clean up paid features check 2026-01-22 15:18:27 -08:00
Owen 9a5bcb9099 Hiring 2026-01-22 15:18:27 -08:00
miloschwartz 8eb6bb2a95 dont include posture in repsonse if not licensed or subscribed 2026-01-22 15:18:27 -08:00
miloschwartz 2aa65ccab3 add mobile links to download banner 2026-01-22 15:18:27 -08:00
miloschwartz be1577a3e7 remove biometric support from ios 2026-01-22 15:18:27 -08:00
miloschwartz c8e1b3bf29 rename windowsDefenderEnabled 2026-01-22 15:18:27 -08:00
Owen e17b986628 Dont show bio info on android 2026-01-22 15:18:27 -08:00
Owen 5f19918ca0 Show the source in the UI 2026-01-22 15:16:41 -08:00
Owen 2959ad0e70 Fix the source of the cli blueprint 2026-01-22 15:03:04 -08:00
miloschwartz a76eec7bb7 add ios and android to readme 2026-01-22 11:27:24 -08:00
miloschwartz 068b2a0dcd clean up paid features check 2026-01-22 11:16:27 -08:00
Owen 316b7e5653 Hiring 2026-01-22 10:38:32 -08:00
miloschwartz 00fc1da33c dont include posture in repsonse if not licensed or subscribed 2026-01-22 10:36:52 -08:00
miloschwartz 9ef93df54f add mobile links to download banner 2026-01-21 18:16:16 -08:00
miloschwartz fd9fdf6399 remove biometric support from ios 2026-01-21 18:13:12 -08:00
miloschwartz 8fa1701e06 rename windowsDefenderEnabled 2026-01-21 17:57:20 -08:00
Owen 4abe83f8a9 Dont show bio info on android 2026-01-21 16:36:35 -08:00
Owen 0a7564acb6 Fix not detecting rc release in sign and package 2026-01-21 16:14:00 -08:00
miloschwartz db0f7cfbae add autoupdates to macos 2026-01-21 14:43:48 -08:00
Owen Schwartz 1724885371 New translations en-us.json (Norwegian Bokmal) 2026-01-21 14:41:55 -08:00
Owen Schwartz a97e9ea8b1 New translations en-us.json (Chinese Simplified) 2026-01-21 14:41:55 -08:00
Owen Schwartz 9d30e97526 New translations en-us.json (Turkish) 2026-01-21 14:41:55 -08:00
Owen Schwartz b91330a27a New translations en-us.json (Russian) 2026-01-21 14:41:55 -08:00
Owen Schwartz 744bc9ebe9 New translations en-us.json (Portuguese) 2026-01-21 14:41:55 -08:00
Owen Schwartz 89ed9e6d7f New translations en-us.json (Polish) 2026-01-21 14:41:55 -08:00
Owen Schwartz b007e7f54a New translations en-us.json (Dutch) 2026-01-21 14:41:55 -08:00
Owen Schwartz 6651a6df42 New translations en-us.json (Korean) 2026-01-21 14:41:55 -08:00
Owen Schwartz 3f29b165aa New translations en-us.json (Italian) 2026-01-21 14:41:55 -08:00
Owen Schwartz b13b91face New translations en-us.json (German) 2026-01-21 14:41:55 -08:00
Owen Schwartz 63c14fe2d5 New translations en-us.json (Czech) 2026-01-21 14:41:55 -08:00
Owen Schwartz 14e74ed02d New translations en-us.json (Bulgarian) 2026-01-21 14:41:55 -08:00
Owen Schwartz 7e30750618 New translations en-us.json (Spanish) 2026-01-21 14:41:55 -08:00
Owen Schwartz 4d1dd16be5 New translations en-us.json (French) 2026-01-21 14:41:55 -08:00
Owen Schwartz fa49cf5eba New translations en-us.json (Norwegian Bokmal) 2026-01-21 14:41:55 -08:00
Owen Schwartz 26b39fc1c6 New translations en-us.json (Chinese Simplified) 2026-01-21 14:41:55 -08:00
Owen Schwartz 0d36e368ea New translations en-us.json (Turkish) 2026-01-21 14:41:55 -08:00
Owen Schwartz 859f265c68 New translations en-us.json (Russian) 2026-01-21 14:41:55 -08:00
Owen Schwartz 3219f520ba New translations en-us.json (Portuguese) 2026-01-21 14:41:55 -08:00
Owen Schwartz 97e27b6caf New translations en-us.json (Polish) 2026-01-21 14:41:55 -08:00
Owen Schwartz 09da83a72b New translations en-us.json (Dutch) 2026-01-21 14:41:55 -08:00
Owen Schwartz d13b210e2f New translations en-us.json (Korean) 2026-01-21 14:41:55 -08:00
Owen Schwartz 09fb672718 New translations en-us.json (Italian) 2026-01-21 14:41:55 -08:00
Owen Schwartz 9797ad0e17 New translations en-us.json (German) 2026-01-21 14:41:55 -08:00
Owen Schwartz 8b3d61ac36 New translations en-us.json (Czech) 2026-01-21 14:41:55 -08:00
Owen Schwartz 7161c9547a New translations en-us.json (Bulgarian) 2026-01-21 14:41:55 -08:00
Owen Schwartz 60d4362a87 New translations en-us.json (Spanish) 2026-01-21 14:41:55 -08:00
Owen Schwartz 1836e0c8fc New translations en-us.json (French) 2026-01-21 14:41:55 -08:00
Owen Schwartz d3344aeb34 New translations en-us.json (Norwegian Bokmal) 2026-01-21 14:41:55 -08:00
Owen Schwartz cfeb093fa6 New translations en-us.json (Chinese Simplified) 2026-01-21 14:41:55 -08:00
Owen Schwartz a469b3ffcc New translations en-us.json (Turkish) 2026-01-21 14:41:55 -08:00
Owen Schwartz 14b3a3fdd8 New translations en-us.json (Russian) 2026-01-21 14:41:55 -08:00
Owen Schwartz 94367ce387 New translations en-us.json (Portuguese) 2026-01-21 14:41:55 -08:00
Owen Schwartz 5be518aa50 New translations en-us.json (Polish) 2026-01-21 14:41:55 -08:00
Owen Schwartz d059a8da9e New translations en-us.json (Dutch) 2026-01-21 14:41:55 -08:00
Owen Schwartz 1dcacbef7a New translations en-us.json (Korean) 2026-01-21 14:41:55 -08:00
Owen Schwartz a25edeccf7 New translations en-us.json (Italian) 2026-01-21 14:41:55 -08:00
Owen Schwartz 315f73c77d New translations en-us.json (German) 2026-01-21 14:41:55 -08:00
Owen Schwartz 666288fccc New translations en-us.json (Czech) 2026-01-21 14:41:55 -08:00
Owen Schwartz 0ccf61c2a9 New translations en-us.json (Bulgarian) 2026-01-21 14:41:55 -08:00
Owen Schwartz c16b1b27a3 New translations en-us.json (Spanish) 2026-01-21 14:41:55 -08:00
Owen Schwartz ed9ba60be6 New translations en-us.json (French) 2026-01-21 14:41:55 -08:00
Owen 24d047e3d8 Merge branch 'main' into dev 2026-01-21 14:39:15 -08:00
miloschwartz 9671079ffb show alias address in private resources table 2026-01-21 14:30:42 -08:00
Owen 688892523c Bump version 2026-01-21 14:24:29 -08:00
miloschwartz b02c341f62 add delete client/device cli command 2026-01-21 12:47:04 -08:00
miloschwartz 3e9bcada1e dont show pending/deny approvals in oss 2026-01-21 12:33:33 -08:00
Milo Schwartz 93d4bd6438 Merge pull request #2280 from Fredkiss3/feat/show-newt-install-command
feat: show install commands in sites & clients details page
2026-01-21 12:27:03 -08:00
miloschwartz 5146498b33 remove extra logs 2026-01-21 12:11:29 -08:00
miloschwartz 72da4f39a8 show disk encryption for mac 2026-01-21 11:49:25 -08:00
miloschwartz a2b2fb804b show device model for ios and android 2026-01-21 11:49:25 -08:00
Owen 3eac80e666 We dont care if its archived when recovering the olm 2026-01-20 20:47:35 -08:00
Fred KISSIE 718d2122a4 ♻️ move olm install command to its own component 2026-01-21 05:22:49 +01:00
Fred KISSIE 310c6c90a3 Merge branch 'dev' into feat/show-newt-install-command 2026-01-21 03:26:52 +01:00
Fred KISSIE 9d80f62d58 ♻️ move newt install commands to its own component for reusing in details 2026-01-21 03:21:35 +01:00
Owen 77032fc989 Remove extranious file 2026-01-20 18:07:28 -08:00
miloschwartz 64e6086f0c set docs link for approvals 2026-01-20 17:50:07 -08:00
miloschwartz 3aa58fdc8f add display info for device posture 2026-01-20 17:47:01 -08:00
Owen Schwartz 93bc6ba615 New translations en-us.json (Norwegian Bokmal) 2026-01-20 16:46:05 -08:00
Owen Schwartz 36690d63cb New translations en-us.json (Chinese Simplified) 2026-01-20 16:46:05 -08:00
Owen Schwartz 9896e9799a New translations en-us.json (Turkish) 2026-01-20 16:46:05 -08:00
Owen Schwartz 27afc82b79 New translations en-us.json (Russian) 2026-01-20 16:46:05 -08:00
Owen Schwartz 1c8f01ce7b New translations en-us.json (Portuguese) 2026-01-20 16:46:05 -08:00
Owen Schwartz 4038ccff0d New translations en-us.json (Polish) 2026-01-20 16:46:05 -08:00
Owen Schwartz 5b41bc2f59 New translations en-us.json (Dutch) 2026-01-20 16:46:05 -08:00
Owen Schwartz 014ba760b5 New translations en-us.json (Korean) 2026-01-20 16:46:05 -08:00
Owen Schwartz 96a91ccf09 New translations en-us.json (Italian) 2026-01-20 16:46:05 -08:00
Owen Schwartz 347fbd2a48 New translations en-us.json (German) 2026-01-20 16:46:05 -08:00
Owen Schwartz 29723052ab New translations en-us.json (Czech) 2026-01-20 16:46:05 -08:00
Owen Schwartz 86415d675b New translations en-us.json (Bulgarian) 2026-01-20 16:46:05 -08:00
Owen Schwartz 8fc4a0dc48 New translations en-us.json (Spanish) 2026-01-20 16:46:05 -08:00
Owen Schwartz e14670cdda New translations en-us.json (French) 2026-01-20 16:46:05 -08:00
Jan-Filip Grosse 4d73488f0c updated the sync and creation of new rules objects to include priorities passed by blueprints. 2026-01-20 15:22:12 -08:00
Jan-Filip Grosse 46e62b24cf Updated RuleSchema to include priority as optional int() value. Included validiation to make sure that no priorities are duplicated (including those which get auto-assigned). 2026-01-20 15:22:12 -08:00
Owen 17c3041fe9 Add migrations 2026-01-20 15:20:19 -08:00
Varun Narravula d5ae381528 feat(fingerprint): clean up stale snapshots older than 1 year 2026-01-20 12:13:43 -08:00
Varun Narravula e2e09527ec fix(fingerprint): set fingerprintId reference to null 2026-01-20 12:13:43 -08:00
Varun Narravula 3ce1afbcc9 feat(fingerprint): consolidate posture checks into fingerprint table 2026-01-20 12:13:43 -08:00
Varun Narravula 1f077d7ec2 refactor(fingerprint): start taking fingerprint snapshots in new table 2026-01-20 12:13:43 -08:00
miloschwartz adf3d0347b remove icon 2026-01-20 11:02:06 -08:00
miloschwartz 7ed8b16a53 fix credenza dialog spacing on mobile 2026-01-20 10:18:17 -08:00
miloschwartz 9f7c162107 make approvals placeholder more mobile friendly 2026-01-19 22:02:02 -08:00
miloschwartz fb15f8cde6 add placeholder approvals ui 2026-01-19 21:57:28 -08:00
miloschwartz 45ecfcc6bb add approve and deny actions to devices table 2026-01-19 21:41:12 -08:00
miloschwartz c6f947e470 fix connected col translations 2026-01-19 21:34:14 -08:00
miloschwartz adf5caf18a add product banner to approvals page 2026-01-19 21:30:29 -08:00
miloschwartz 0b8068e13d add pending approvals count to sidebar 2026-01-19 21:25:28 -08:00
miloschwartz f143d2e214 make default filter in approvals be pending 2026-01-19 21:14:33 -08:00
miloschwartz 2e802301ae make client link work approval feed 2026-01-19 21:07:15 -08:00
miloschwartz 7305c721a6 format device approval message 2026-01-19 21:00:48 -08:00
miloschwartz b299f3d6aa use display name function 2026-01-19 21:00:48 -08:00
Fred KISSIE e09cd6c16c ♻️ reset firn 2026-01-19 20:39:24 -08:00
Fred KISSIE b7df8b7319 ♻️ make logo URL optional 2026-01-19 20:39:24 -08:00
Fred KISSIE c92b5942fc 💄 fix analytics refresh button align 2026-01-19 20:39:24 -08:00
Fred KISSIE fe729ec762 🚧wip: command component 2026-01-20 05:21:18 +01:00
miloschwartz 915673798e update updateRole endpoint 2026-01-19 20:20:31 -08:00
miloschwartz 9527fe4f26 add update role openapi registry 2026-01-19 20:12:35 -08:00
miloschwartz e8a8b3f664 remove beta tag for clients 2026-01-19 20:10:30 -08:00
Fred KISSIE d6a829abc2 Merge branch 'dev' into feat/show-newt-install-command 2026-01-20 03:36:38 +01:00
Owen 1a36cd0317 Fix linting errors 2026-01-19 17:57:55 -08:00
Kolin 75005ccf81 Fix WireGuard QR code layout on mobile in site creation page 2026-01-19 17:52:21 -08:00
Kolin fd6c600531 Fix WireGuard QR code layout on mobile in site credentials page 2026-01-19 17:52:21 -08:00
ThanatosDi 6996c2501e feat: zh-TW for v1.14.1 2026-01-19 17:51:30 -08:00
Owen efbd9bdb56 Remove faker 2026-01-19 17:50:46 -08:00
dependabot[bot] 0d34213647 Bump the prod-minor-updates group across 1 directory with 12 updates
Bumps the prod-minor-updates group with 12 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [@asteasolutions/zod-to-openapi](https://github.com/asteasolutions/zod-to-openapi) | `8.2.0` | `8.4.0` |
| [@aws-sdk/client-s3](https://github.com/aws/aws-sdk-js-v3/tree/HEAD/clients/client-s3) | `3.955.0` | `3.971.0` |
| [@faker-js/faker](https://github.com/faker-js/faker) | `10.1.0` | `10.2.0` |
| [ioredis](https://github.com/luin/ioredis) | `5.8.2` | `5.9.2` |
| [next-intl](https://github.com/amannn/next-intl) | `4.6.1` | `4.7.0` |
| [pg](https://github.com/brianc/node-postgres/tree/HEAD/packages/pg) | `8.16.3` | `8.17.1` |
| [posthog-node](https://github.com/PostHog/posthog-js/tree/HEAD/packages/node) | `5.17.4` | `5.21.1` |
| [react-hook-form](https://github.com/react-hook-form/react-hook-form) | `7.68.0` | `7.71.1` |
| [resend](https://github.com/resend/resend-node) | `6.6.0` | `6.7.0` |
| [stripe](https://github.com/stripe/stripe-node) | `20.1.0` | `20.2.0` |
| [ws](https://github.com/websockets/ws) | `8.18.3` | `8.19.0` |
| [zod](https://github.com/colinhacks/zod) | `4.2.1` | `4.3.5` |



Updates `@asteasolutions/zod-to-openapi` from 8.2.0 to 8.4.0
- [Release notes](https://github.com/asteasolutions/zod-to-openapi/releases)
- [Commits](https://github.com/asteasolutions/zod-to-openapi/compare/v8.2.0...v8.4.0)

Updates `@aws-sdk/client-s3` from 3.955.0 to 3.971.0
- [Release notes](https://github.com/aws/aws-sdk-js-v3/releases)
- [Changelog](https://github.com/aws/aws-sdk-js-v3/blob/main/clients/client-s3/CHANGELOG.md)
- [Commits](https://github.com/aws/aws-sdk-js-v3/commits/v3.971.0/clients/client-s3)

Updates `@faker-js/faker` from 10.1.0 to 10.2.0
- [Release notes](https://github.com/faker-js/faker/releases)
- [Changelog](https://github.com/faker-js/faker/blob/next/CHANGELOG.md)
- [Commits](https://github.com/faker-js/faker/compare/v10.1.0...v10.2.0)

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

Updates `next-intl` from 4.6.1 to 4.7.0
- [Release notes](https://github.com/amannn/next-intl/releases)
- [Changelog](https://github.com/amannn/next-intl/blob/main/CHANGELOG.md)
- [Commits](https://github.com/amannn/next-intl/compare/v4.6.1...v4.7.0)

Updates `pg` from 8.16.3 to 8.17.1
- [Changelog](https://github.com/brianc/node-postgres/blob/master/CHANGELOG.md)
- [Commits](https://github.com/brianc/node-postgres/commits/pg@8.17.1/packages/pg)

Updates `posthog-node` from 5.17.4 to 5.21.1
- [Release notes](https://github.com/PostHog/posthog-js/releases)
- [Changelog](https://github.com/PostHog/posthog-js/blob/main/packages/node/CHANGELOG.md)
- [Commits](https://github.com/PostHog/posthog-js/commits/posthog-node@5.21.1/packages/node)

Updates `react-hook-form` from 7.68.0 to 7.71.1
- [Release notes](https://github.com/react-hook-form/react-hook-form/releases)
- [Changelog](https://github.com/react-hook-form/react-hook-form/blob/master/CHANGELOG.md)
- [Commits](https://github.com/react-hook-form/react-hook-form/compare/v7.68.0...v7.71.1)

Updates `resend` from 6.6.0 to 6.7.0
- [Release notes](https://github.com/resend/resend-node/releases)
- [Commits](https://github.com/resend/resend-node/compare/v6.6.0...v6.7.0)

Updates `stripe` from 20.1.0 to 20.2.0
- [Release notes](https://github.com/stripe/stripe-node/releases)
- [Changelog](https://github.com/stripe/stripe-node/blob/master/CHANGELOG.md)
- [Commits](https://github.com/stripe/stripe-node/compare/v20.1.0...v20.2.0)

Updates `ws` from 8.18.3 to 8.19.0
- [Release notes](https://github.com/websockets/ws/releases)
- [Commits](https://github.com/websockets/ws/compare/8.18.3...8.19.0)

Updates `zod` from 4.2.1 to 4.3.5
- [Release notes](https://github.com/colinhacks/zod/releases)
- [Commits](https://github.com/colinhacks/zod/compare/v4.2.1...v4.3.5)

---
updated-dependencies:
- dependency-name: "@asteasolutions/zod-to-openapi"
  dependency-version: 8.4.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: prod-minor-updates
- dependency-name: "@aws-sdk/client-s3"
  dependency-version: 3.971.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: prod-minor-updates
- dependency-name: "@faker-js/faker"
  dependency-version: 10.2.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: prod-minor-updates
- dependency-name: ioredis
  dependency-version: 5.9.2
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: prod-minor-updates
- dependency-name: next-intl
  dependency-version: 4.7.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: prod-minor-updates
- dependency-name: pg
  dependency-version: 8.17.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: prod-minor-updates
- dependency-name: posthog-node
  dependency-version: 5.21.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: prod-minor-updates
- dependency-name: react-hook-form
  dependency-version: 7.71.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: prod-minor-updates
- dependency-name: resend
  dependency-version: 6.7.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: prod-minor-updates
- dependency-name: stripe
  dependency-version: 20.2.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: prod-minor-updates
- dependency-name: ws
  dependency-version: 8.19.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: prod-minor-updates
- dependency-name: zod
  dependency-version: 4.3.5
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: prod-minor-updates
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-19 17:50:46 -08:00
dependabot[bot] 870b85d71b Bump the dev-minor-updates group across 1 directory with 3 updates
Bumps the dev-minor-updates group with 3 updates in the / directory: [prettier](https://github.com/prettier/prettier), [react-email](https://github.com/resend/react-email/tree/HEAD/packages/react-email) and [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint).


Updates `prettier` from 3.7.4 to 3.8.0
- [Release notes](https://github.com/prettier/prettier/releases)
- [Changelog](https://github.com/prettier/prettier/blob/main/CHANGELOG.md)
- [Commits](https://github.com/prettier/prettier/compare/3.7.4...3.8.0)

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

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

---
updated-dependencies:
- dependency-name: prettier
  dependency-version: 3.8.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: dev-minor-updates
- dependency-name: react-email
  dependency-version: 5.2.5
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: dev-minor-updates
- dependency-name: typescript-eslint
  dependency-version: 8.53.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: dev-minor-updates
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-19 17:42:02 -08:00
dependabot[bot] 86ba6b6f86 Bump golang.org/x/term in /install in the prod-minor-updates group
Bumps the prod-minor-updates group in /install with 1 update: [golang.org/x/term](https://github.com/golang/term).


Updates `golang.org/x/term` from 0.38.0 to 0.39.0
- [Commits](https://github.com/golang/term/compare/v0.38.0...v0.39.0)

---
updated-dependencies:
- dependency-name: golang.org/x/term
  dependency-version: 0.39.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: prod-minor-updates
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-19 17:39:29 -08:00
dependabot[bot] 02be3cd0c4 Bump qs from 6.14.0 to 6.14.1
Bumps [qs](https://github.com/ljharb/qs) from 6.14.0 to 6.14.1.
- [Changelog](https://github.com/ljharb/qs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/ljharb/qs/compare/v6.14.0...v6.14.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-19 17:39:00 -08:00
dependabot[bot] 1b756ef9a0 Bump aws-actions/configure-aws-credentials from 2 to 5
Bumps [aws-actions/configure-aws-credentials](https://github.com/aws-actions/configure-aws-credentials) from 2 to 5.
- [Release notes](https://github.com/aws-actions/configure-aws-credentials/releases)
- [Changelog](https://github.com/aws-actions/configure-aws-credentials/blob/main/CHANGELOG.md)
- [Commits](https://github.com/aws-actions/configure-aws-credentials/compare/v2...v5)

---
updated-dependencies:
- dependency-name: aws-actions/configure-aws-credentials
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-19 17:38:03 -08:00
dependabot[bot] ceda06f9ae Bump actions/setup-go from 6.1.0 to 6.2.0
Bumps [actions/setup-go](https://github.com/actions/setup-go) from 6.1.0 to 6.2.0.
- [Release notes](https://github.com/actions/setup-go/releases)
- [Commits](https://github.com/actions/setup-go/compare/4dc6199c7b1a012772edbd06daecab0f50c9053c...7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-19 17:37:55 -08:00
dependabot[bot] 068eba015b Bump actions/setup-node from 6.1.0 to 6.2.0
Bumps [actions/setup-node](https://github.com/actions/setup-node) from 6.1.0 to 6.2.0.
- [Release notes](https://github.com/actions/setup-node/releases)
- [Commits](https://github.com/actions/setup-node/compare/395ad3262231945c25e8478fd5baf05154b1d79f...6044e13b5dc448c55e2357c09f80417699197238)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-19 17:37:46 -08:00
Owen 7ae6b2df05 Fix email parsing validation error? 2026-01-19 16:45:15 -08:00
Owen 6765d5ad26 Reorder setting the olm agent and version 2026-01-19 16:30:34 -08:00
Owen 35cfd6bec9 Move up figerprint so it happens before block 2026-01-19 14:59:00 -08:00
Owen 90f66baf85 Update config dir values to match install 2026-01-19 13:55:22 -08:00
miloschwartz 5edfed78f2 fix only show advanced toggle on newt sites 2026-01-19 11:50:56 -08:00
miloschwartz fd6a3e5a17 fix default logo size 2026-01-19 11:47:14 -08:00
miloschwartz 14a4b1b4b4 add clear license key command to pangctl 2026-01-19 11:39:58 -08:00
Kolin 5743c0bb72 chore: add ru-RU, bg-BG and cs-CZ locales 2026-01-19 10:11:50 -08:00
miloschwartz acca1b6a91 improve red alert colors 2026-01-18 22:10:34 -08:00
miloschwartz 355265cd1e show paid user alert on approvals 2026-01-18 21:49:15 -08:00
miloschwartz 6ec8d143fa hide pending approval filter in oss 2026-01-18 21:47:00 -08:00
miloschwartz 8ae327e8f5 fix org policy check 2026-01-18 21:24:17 -08:00
Owen c03a61f613 Delete each of the site resources and rebuild 2026-01-18 15:00:08 -08:00
miloschwartz 89928c753c add server info endpoint 2026-01-18 12:19:07 -08:00
miloschwartz a56fcc0fba add olm container install commands 2026-01-18 12:11:58 -08:00
miloschwartz 43c60bcdbc spacing and phrase improvement 2026-01-18 12:08:29 -08:00
miloschwartz a3fa12f0e4 split org security settings to new tab 2026-01-18 12:03:01 -08:00
Owen d696556097 Handle disconnecting message when stoppng 2026-01-18 11:55:41 -08:00
miloschwartz 6a45151741 show fingerprint popup and fix policy check errors 2026-01-18 11:55:24 -08:00
miloschwartz 34e2fbefb9 add view user device page with fingerprint and actions 2026-01-17 20:59:20 -08:00
Owen f7cede4713 Use /etc/sysctl.d/99-podman.conf
Fixes #2253
2026-01-17 12:22:43 -08:00
Owen 610b20c1ff Use the right driver
Fixes #2254
2026-01-17 12:21:47 -08:00
miloschwartz fb19e10cdc Merge branch 'dev' into bubble-errors-up 2026-01-17 12:00:55 -08:00
miloschwartz 2f1756ccf2 add more error messages for org access policy 2026-01-17 12:00:27 -08:00
Owen ce632a25cf Consolidate the messages into the same enum 2026-01-17 11:41:10 -08:00
Fred KISSIE ec10c37468 🚧wip 2026-01-17 02:09:59 +01:00
Fred KISSIE 5ee3e140ed 🔇 fix logs 2026-01-17 02:07:43 +01:00
Owen 888f5f8bb6 Dont terminate on archive 2026-01-16 17:06:16 -08:00
Owen 9114dd5992 Send terminate error messages 2026-01-16 14:57:54 -08:00
Owen a126494c12 Add pending 2026-01-16 14:37:06 -08:00
Milo Schwartz 79ba804c88 Merge pull request #2252 from Fredkiss3/fix/request-analytics-loading-state
Fix: better loading state for analytics
2026-01-16 14:35:55 -08:00
Owen e2cbe11a5f Send error codes down to olm 2026-01-16 14:19:36 -08:00
Owen 05748bf8ff Merge branch 'dev' into msg-delivery 2026-01-16 12:22:23 -08:00
Owen f8c98bf6bf Fix log messages 2026-01-16 12:19:52 -08:00
Fred KISSIE f4496bb23a ♻️ show all country list 2026-01-16 17:36:48 +01:00
Fred KISSIE c93766bb48 💄fix countries list grid items 2026-01-16 17:35:17 +01:00
Owen a1ea3f74b3 Move the query into the sync 2026-01-15 22:00:13 -08:00
Milo Schwartz 06aaa7c680 Merge pull request #2121 from Fredkiss3/feat/device-approvals
feat: device approvals
2026-01-15 21:33:31 -08:00
Owen 65e8bfc93e Message syncing works 2026-01-15 21:26:13 -08:00
miloschwartz ff5e12655f add pretty apple device names 2026-01-15 17:59:45 -08:00
Fred KISSIE 1065004fa3 🚸 show a better loading state for analytics 2026-01-16 02:07:08 +01:00
Fred KISSIE 6d90d734f4 🏷️ fix types 2026-01-15 23:25:05 +01:00
Varun Narravula 6c8757f230 feat(olm): reset/send new olm secret if a matching fingerprint is detected 2026-01-15 12:33:26 -08:00
Milo Schwartz 40e37b1798 Merge pull request #2244 from water-sucks/add-fingerprint-and-posture-check-info
feat(fingerprint): store posture checks and fingerprint info
2026-01-15 12:05:41 -08:00
miloschwartz 8e1fd4474f fix whitelist hyrdration closes #2190 2026-01-14 22:09:56 -08:00
miloschwartz bd87585396 fix whitelist hyrdration closes #2190 2026-01-14 22:08:16 -08:00
Varun Narravula e9e935d6c4 feat(fingerprint): add platform fingerprint hash 2026-01-14 20:21:22 -08:00
miloschwartz 2f2c2b4222 improved org idp login flow 2026-01-14 19:15:19 -08:00
Fred KISSIE 9749a272ec 🏷️fix types 2026-01-15 03:46:25 +01:00
Fred KISSIE b76a50238e 🏷️ fix types 2026-01-15 03:40:30 +01:00
Fred KISSIE a4f3963a5a ♻️update approval filter & set approval to denied when blocked 2026-01-15 03:34:42 +01:00
Owen d52bd65d21 Fix build 2026-01-14 17:54:34 -08:00
Fred KISSIE fb51f42f35 ♻️ set approval & blocked work in tandem 2026-01-15 01:33:52 +01:00
Fred KISSIE c910a715bd ♻️ set blocked to true if approvalState is set to denied 2026-01-15 01:12:38 +01:00
Fred KISSIE 9040f9b82a ♻️ set approval state nullable 2026-01-15 01:03:02 +01:00
Fred KISSIE fc0ec0d754 🐛 remove unused approval state 2026-01-15 00:28:30 +01:00
Fred KISSIE b3569174b6 🐛 fix sqlite type 2026-01-15 00:20:45 +01:00
Fred KISSIE 0cae624995 🏷️ fix types 2026-01-14 23:57:16 +01:00
Fred KISSIE cbf184342b Merge branch 'dev' into feat/device-approvals 2026-01-14 23:08:40 +01:00
Fred KISSIE ce123a7f1a 💬 make the message more descriptive 2026-01-14 03:37:47 +01:00
Fred KISSIE 0c5daa7173 process approvals on the frontend 2026-01-14 03:31:49 +01:00
Fred KISSIE bc20a34a49 process approval endpoint 2026-01-14 03:00:40 +01:00
Fred KISSIE d5b6a426a9 💄 filter by approval state 2026-01-14 02:24:11 +01:00
Fred KISSIE 4c78e93143 💄 show approval state in the user device uI 2026-01-14 01:59:51 +01:00
miloschwartz 5f184e9e5e support background image on org auth pages 2026-01-13 16:35:27 -08:00
miloschwartz 2201b0395d add optional tags field to idp 2026-01-13 16:21:40 -08:00
miloschwartz 51818044b1 fix broken redirect url on custom auth url login 2026-01-13 15:48:07 -08:00
Fred KISSIE 30943010e6 ♻️ sort by pending first 2026-01-14 00:37:48 +01:00
Fred KISSIE dd5ca10226 ♻️ empty data 2026-01-14 00:35:35 +01:00
miloschwartz a56b058858 fix role name missing in forward headers 2026-01-13 15:28:02 -08:00
miloschwartz eade72e2c6 set text-destructive color 2026-01-13 09:36:53 -08:00
miloschwartz e9bc9747b8 check if olm is blocked in get user olm 2026-01-12 22:08:58 -08:00
Owen eb0cdda0f9 Merge branch 'dev' into msg-delivery 2026-01-12 21:17:38 -08:00
Owen 552adf3200 Properly handle blocked devices 2026-01-12 21:14:18 -08:00
Owen eba25fcc4d Add increment options and slight cleanup 2026-01-12 20:48:18 -08:00
miloschwartz 673cd0fcd1 add block client 2026-01-12 20:37:53 -08:00
miloschwartz b941b5571f add archive to org clients and add unarchive 2026-01-12 15:52:27 -08:00
Owen ca026b41c0 Merge branch 'main' into dev 2026-01-11 14:19:59 -08:00
Owen 29a683a815 Copy all tags to github reg 2026-01-11 14:19:38 -08:00
Owen 69dbd20ea5 Use same regex for blueprint aliases
Closes #2218
Fixes #2216
2026-01-11 13:39:46 -08:00
miloschwartz 427ee026ac Merge branch 'org-only-idp' into dev 2026-01-11 10:47:57 -08:00
miloschwartz 0a537c6830 add org only idp to integration api 2026-01-11 10:47:19 -08:00
Owen 89682a2ee4 Try to intent:// into android app from tab 2026-01-11 10:39:39 -08:00
Owen 78b00a18cc Add retry to aquire 2026-01-11 10:39:28 -08:00
Owen 192702daf9 Quiet log 2026-01-11 10:39:18 -08:00
Varun Narravula fcee735578 feat(fingerprints): receive fingerprints/postures from olm and add to db 2026-01-10 21:15:54 -08:00
miloschwartz 2ba49e84bb add archive device instead of delete 2026-01-09 18:00:00 -08:00
Fred KISSIE 262376aa75 approval list UI 2026-01-10 02:37:50 +01:00
miloschwartz 4c8d2266ec clean up login page 2026-01-09 14:41:22 -08:00
miloschwartz bb98bf03aa Merge branch 'org-only-idp' into dev 2026-01-09 13:34:52 -08:00
Fred KISSIE 19c3efc9e9 🚧 working on the approval feed 2026-01-09 02:20:08 +01:00
Fred KISSIE 7164721ee0 🐛 insert timestamp correctly 2026-01-09 01:50:56 +01:00
Fred KISSIE 74b16809ec ♻️ update endpoint to only return relevant data 2026-01-09 01:40:15 +01:00
Fred KISSIE 220723d25f ♻️ component refactor 2026-01-09 01:33:52 +01:00
Fred KISSIE fdb03c9626 ♻️ list approvals with client & user data 2026-01-09 01:33:40 +01:00
Fred KISSIE a81bbb9192 create approval request and mark client approval as pending if the user's role requires it 2026-01-09 01:18:15 +01:00
Fred KISSIE 7a4aff8e4b 🗃️ use clientId and fix bad column name for decision and add userId 2026-01-09 01:17:05 +01:00
miloschwartz 2810632f4a add flag to enable org only idp in ee 2026-01-07 20:40:59 -08:00
Fred KISSIE 2d0dd067b8 ♻️ refactor 2026-01-08 03:41:09 +01:00
Fred KISSIE 3ab25f5ff1 ♻️ refactor 2026-01-08 03:38:55 +01:00
Fred KISSIE 39bebea5f7 create & update role with device approval 2026-01-08 03:33:03 +01:00
miloschwartz 57681dcd3d remove artificial delay 2026-01-07 12:06:50 -08:00
miloschwartz 168ce549f7 remove guards form list idp for integration api 2026-01-06 13:20:18 -05:00
Owen 9ec94441f3 Try to open apps 2026-01-05 21:46:38 -05:00
Owen 53e7b99605 Quiet up logs 2026-01-05 21:25:15 -05:00
Fred KISSIE abfe476cb9 🚧 wip 2026-01-06 02:02:09 +01:00
Fred KISSIE bbca200ceb 🙈 do not include claude.md in gitignore 2026-01-06 01:51:54 +01:00
Fred KISSIE cb21cab117 🚧 add device approval in the roles page 2026-01-06 01:51:33 +01:00
Fred KISSIE 1f80845a7a 🗃️ move approval state to client directly where it makes more sense 2026-01-05 22:49:42 +01:00
Owen 20088ef82b Log in to ecr 2026-01-05 11:31:29 -05:00
Owen 1e0b1a3607 Add missing \ 2026-01-05 11:23:10 -05:00
Owen 24e8455c73 Remove aws cli call 2026-01-05 11:20:25 -05:00
Owen e42a732e93 Add saas workflow 2026-01-05 11:16:30 -05:00
Fred KISSIE 0f2b94307f Merge branch 'dev' into feat/device-approvals 2026-01-05 16:54:18 +01:00
Owen d333cb5199 Add encoded chars to default traefik config
Closes #2176
2026-01-05 10:37:18 -05:00
Owen a6db4f20ad Expand where org id is pulled for subscription 2026-01-05 10:34:11 -05:00
Jack Myers 9ed9472c01 Fix spelling mistake in installer version prompt 2026-01-02 10:18:21 -05:00
Owen f7fcde8312 Add max recursion depth to matchSegments 2025-12-31 10:40:16 -05:00
Owen 6660c850f3 Try to bound logs
Ref #2120
2025-12-31 10:31:40 -05:00
Owen 8a08bdf9f0 Add OCI labels
Fixes #2170
2025-12-29 12:29:27 -05:00
Owen 87807e22e0 Add encoded chars to default traefik config
Closes #2176
2025-12-29 10:49:32 -05:00
Owen 0eb39abdb4 Set hc to unknown when changing to local site
Fixes #2181
2025-12-29 10:22:06 -05:00
miloschwartz a499ebc158 add badger to dyn config example 2025-12-29 10:17:26 -05:00
ruxenburg 9467e6c032 improve delete confirmation logic 2025-12-27 22:20:50 -05:00
ruxenburg 9d849a0ced Fix confirm delete button to require confirmation text before enabling it. 2025-12-27 22:20:50 -05:00
Owen Schwartz 2ca400ab16 New translations en-us.json (French) 2025-12-24 16:14:26 -05:00
Owen Schwartz 4183067c77 New translations en-us.json (Norwegian Bokmal) 2025-12-24 16:14:26 -05:00
Owen Schwartz 5eb4691973 New translations en-us.json (Chinese Simplified) 2025-12-24 16:14:26 -05:00
Owen Schwartz d14dfbf360 New translations en-us.json (Turkish) 2025-12-24 16:14:26 -05:00
Owen Schwartz 493a5ad02a New translations en-us.json (Russian) 2025-12-24 16:14:26 -05:00
Owen Schwartz 481beff028 New translations en-us.json (Portuguese) 2025-12-24 16:14:26 -05:00
Owen Schwartz f1f7e438b4 New translations en-us.json (Polish) 2025-12-24 16:14:26 -05:00
Owen Schwartz 00f84c9d8e New translations en-us.json (Dutch) 2025-12-24 16:14:26 -05:00
Owen Schwartz f75b9c6c86 New translations en-us.json (Korean) 2025-12-24 16:14:26 -05:00
Owen Schwartz 31bc6d5773 New translations en-us.json (Italian) 2025-12-24 16:14:26 -05:00
Owen Schwartz 51dc1450d3 New translations en-us.json (German) 2025-12-24 16:14:26 -05:00
Owen Schwartz fcbea08c87 New translations en-us.json (Czech) 2025-12-24 16:14:26 -05:00
Owen Schwartz 8d60a87aa1 New translations en-us.json (Bulgarian) 2025-12-24 16:14:26 -05:00
Owen Schwartz 956aa64519 New translations en-us.json (Spanish) 2025-12-24 16:14:26 -05:00
Owen Schwartz fd1cb6ca23 New translations en-us.json (French) 2025-12-24 16:14:26 -05:00
Owen Schwartz 37082ae436 New translations en-us.json (Norwegian Bokmal) 2025-12-24 16:14:26 -05:00
Owen Schwartz bb47ca3d2e New translations en-us.json (Chinese Simplified) 2025-12-24 16:14:26 -05:00
Owen Schwartz 0dd3c84b24 New translations en-us.json (Turkish) 2025-12-24 16:14:26 -05:00
Owen Schwartz 848fca7e1b New translations en-us.json (Russian) 2025-12-24 16:14:26 -05:00
Owen Schwartz 2500f99722 New translations en-us.json (Portuguese) 2025-12-24 16:14:26 -05:00
Owen Schwartz c7737c444f New translations en-us.json (Polish) 2025-12-24 16:14:26 -05:00
Owen Schwartz 4d1a7ed69b New translations en-us.json (Dutch) 2025-12-24 16:14:26 -05:00
Owen Schwartz 626d5df67e New translations en-us.json (Korean) 2025-12-24 16:14:26 -05:00
Owen Schwartz e4c369deec New translations en-us.json (Italian) 2025-12-24 16:14:26 -05:00
Owen Schwartz 307209e73f New translations en-us.json (German) 2025-12-24 16:14:26 -05:00
Owen Schwartz dc84935ee6 New translations en-us.json (Czech) 2025-12-24 16:14:26 -05:00
Owen Schwartz 998c1f52ca New translations en-us.json (Bulgarian) 2025-12-24 16:14:26 -05:00
Owen Schwartz 2766758c66 New translations en-us.json (Spanish) 2025-12-24 16:14:26 -05:00
Owen Schwartz 258d1d82f3 New translations en-us.json (French) 2025-12-24 16:14:26 -05:00
Owen Schwartz 46aaadb76a New translations en-us.json (Norwegian Bokmal) 2025-12-24 16:14:26 -05:00
Owen Schwartz ea7a618810 New translations en-us.json (Chinese Simplified) 2025-12-24 16:14:26 -05:00
Owen Schwartz c0e503b31f New translations en-us.json (Turkish) 2025-12-24 16:14:26 -05:00
Owen Schwartz 55f5a41752 New translations en-us.json (Russian) 2025-12-24 16:14:26 -05:00
Owen Schwartz b0be82be86 New translations en-us.json (Portuguese) 2025-12-24 16:14:26 -05:00
Owen Schwartz 96a9bdb700 New translations en-us.json (Polish) 2025-12-24 16:14:26 -05:00
Owen Schwartz 74e6d39c24 New translations en-us.json (Dutch) 2025-12-24 16:14:26 -05:00
Owen Schwartz 61dfa00222 New translations en-us.json (Korean) 2025-12-24 16:14:26 -05:00
Owen Schwartz 476281db2b New translations en-us.json (Italian) 2025-12-24 16:14:26 -05:00
Owen Schwartz f32e31c73d New translations en-us.json (Czech) 2025-12-24 16:14:26 -05:00
Owen Schwartz ea72279080 New translations en-us.json (Bulgarian) 2025-12-24 16:14:26 -05:00
Owen Schwartz 16ba56af84 New translations en-us.json (Spanish) 2025-12-24 16:14:26 -05:00
Owen Schwartz f13ddde988 New translations en-us.json (Norwegian Bokmal) 2025-12-24 16:14:26 -05:00
Owen Schwartz 67dc10dfe9 New translations en-us.json (Chinese Simplified) 2025-12-24 16:14:26 -05:00
Owen Schwartz 5fd216adc2 New translations en-us.json (Turkish) 2025-12-24 16:14:26 -05:00
Owen Schwartz 6f0268f6c0 New translations en-us.json (Russian) 2025-12-24 16:14:26 -05:00
Owen Schwartz 2996dfb33a New translations en-us.json (Portuguese) 2025-12-24 16:14:26 -05:00
Owen Schwartz c92f2cd4ba New translations en-us.json (Polish) 2025-12-24 16:14:26 -05:00
Owen Schwartz 8164d5c1ad New translations en-us.json (Dutch) 2025-12-24 16:14:26 -05:00
Owen Schwartz d9d8d85f6e New translations en-us.json (Korean) 2025-12-24 16:14:26 -05:00
Owen Schwartz d49720703f New translations en-us.json (Italian) 2025-12-24 16:14:26 -05:00
Owen Schwartz 2362a9b4dd New translations en-us.json (German) 2025-12-24 16:14:26 -05:00
Owen Schwartz a8265a5286 New translations en-us.json (Czech) 2025-12-24 16:14:26 -05:00
Owen Schwartz 9ea7431b73 New translations en-us.json (Bulgarian) 2025-12-24 16:14:26 -05:00
Owen Schwartz 37e6f320fe New translations en-us.json (Spanish) 2025-12-24 16:14:26 -05:00
miloschwartz c0c0d48edf ui enhancements 2025-12-24 16:14:26 -05:00
Owen 284cccbe17 Attempt to fix loginPageOrg undefined error 2025-12-24 16:14:26 -05:00
Owen 81a9a94264 Try to remove deadlocks on client updates 2025-12-24 16:14:26 -05:00
Owen dccf101554 Allow all in country in blueprints
Fixes #2163
2025-12-24 16:14:26 -05:00
Owen a01c06bbc7 Respect http status for url & maintenance mode
Fixes #2164
2025-12-24 16:14:26 -05:00
miloschwartz db43cf1b30 add sticky actions col to org idp table 2025-12-24 16:14:26 -05:00
miloschwartz 2f561b5604 adjustments to mobile header css closes #1930 2025-12-24 16:14:26 -05:00
miloschwartz 5a30f036ff fade mobile footer 2025-12-24 16:14:26 -05:00
miloschwartz 768b9ffd09 fix server admin spacing on mobile sidebar 2025-12-24 16:14:26 -05:00
miloschwartz 8732e50047 add flag to disable product help banners 2025-12-24 16:14:26 -05:00
miloschwartz d6e0024c96 improved button loading animation 2025-12-24 16:14:26 -05:00
miloschwartz 9759e86921 add stripPortFromHost and reuse everywhere 2025-12-24 16:14:26 -05: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 0ccd5714f9 Seperating out functions 2025-12-24 11:50:27 -05:00
Owen e2dfc3eb20 Merge branch 'dev' into msg-delivery 2025-12-24 11:33:41 -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 446eba8bc9 Orging how we are going to make the sync 2025-12-24 10:38:44 -05:00
Owen 18579c0647 Merge branch 'dev' into msg-delivery 2025-12-23 16:57:17 -05:00
Owen 2bb94e24eb Merge branch 'main' into dev 2025-12-23 16:57:01 -05:00
Owen 0d37e08638 Merge branch 'dev' into msg-delivery 2025-12-23 16:56:50 -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
Dennis 3d4df906cf Added missing translations 2025-12-22 18:43:43 +01:00
miloschwartz 71386d3b05 fix request ip port strip issue with badger >=1.3.0 2025-12-22 12:35:40 -05:00
Dennis e051142334 Add region-based resource rule 2025-12-22 17:44:56 +01: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
Owen 75b9703793 Seperate config gen into functions 2025-12-20 11:41:23 -05:00
Fred KISSIE e983e1166a 🚧 wip: approval tables in DB 2025-12-20 00:05:33 +01:00
Owen 322f3bfb1d Add version and send it down 2025-12-19 16:44:57 -05:00
Fred KISSIE 009b86c33b Merge branch 'dev' into feat/device-approvals 2025-12-19 20:03:05 +01:00
Fred KISSIE a5775a0f4f 🗃️ create approvals table 2025-12-19 00:00:10 +01:00
838 changed files with 67057 additions and 25041 deletions
+4 -2
View File
@@ -28,7 +28,9 @@ LICENSE
CONTRIBUTING.md
dist
.git
migrations/
server/migrations/
config/
build.ts
tsconfig.json
tsconfig.json
Dockerfile*
drizzle.config.ts
+1
View File
@@ -0,0 +1 @@
* @oschwartz10612 @miloschwartz
+2 -12
View File
@@ -44,19 +44,9 @@ updates:
schedule:
interval: "daily"
groups:
dev-patch-updates:
dependency-type: "development"
patch-updates:
update-types:
- "patch"
dev-minor-updates:
dependency-type: "development"
minor-updates:
update-types:
- "minor"
prod-patch-updates:
dependency-type: "production"
update-types:
- "patch"
prod-minor-updates:
dependency-type: "production"
update-types:
- "minor"
+219 -54
View File
@@ -1,4 +1,4 @@
name: CI/CD Pipeline
name: Public CICD Pipeline
# CI/CD workflow for building, publishing, mirroring, signing container images and building release binaries.
# Actions are pinned to specific SHAs to reduce supply-chain risk. This workflow triggers on tag push events.
@@ -29,7 +29,7 @@ jobs:
permissions: write-all
steps:
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v2
uses: aws-actions/configure-aws-credentials@v6
with:
role-to-assume: arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/${{ secrets.AWS_ROLE_NAME }}
role-duration-seconds: 3600
@@ -62,7 +62,7 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Monitor storage space
run: |
@@ -77,7 +77,7 @@ jobs:
fi
- name: Log in to Docker Hub
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0
with:
registry: docker.io
username: ${{ secrets.DOCKER_HUB_USERNAME }}
@@ -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
@@ -134,7 +134,7 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Monitor storage space
run: |
@@ -149,7 +149,7 @@ jobs:
fi
- name: Log in to Docker Hub
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0
with:
registry: docker.io
username: ${{ secrets.DOCKER_HUB_USERNAME }}
@@ -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
@@ -201,10 +201,10 @@ jobs:
timeout-minutes: 30
steps:
- name: Checkout code
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Log in to Docker Hub
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0
with:
registry: docker.io
username: ${{ secrets.DOCKER_HUB_USERNAME }}
@@ -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
@@ -256,7 +256,7 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Extract tag name
id: get-tag
@@ -264,9 +264,9 @@ jobs:
shell: bash
- name: Install Go
uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6.1.0
uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0
with:
go-version: 1.24
go-version: 1.25
- name: Update version in package.json
run: |
@@ -289,25 +289,17 @@ jobs:
echo "LATEST_BADGER_TAG=$LATEST_TAG" >> $GITHUB_ENV
shell: bash
- name: Update install/main.go
run: |
PANGOLIN_VERSION=${{ env.TAG }}
GERBIL_VERSION=${{ env.LATEST_GERBIL_TAG }}
BADGER_VERSION=${{ env.LATEST_BADGER_TAG }}
sed -i "s/config.PangolinVersion = \".*\"/config.PangolinVersion = \"$PANGOLIN_VERSION\"/" install/main.go
sed -i "s/config.GerbilVersion = \".*\"/config.GerbilVersion = \"$GERBIL_VERSION\"/" install/main.go
sed -i "s/config.BadgerVersion = \".*\"/config.BadgerVersion = \"$BADGER_VERSION\"/" install/main.go
echo "Updated install/main.go with Pangolin version $PANGOLIN_VERSION, Gerbil version $GERBIL_VERSION, and Badger version $BADGER_VERSION"
cat install/main.go
shell: bash
- name: Build installer
working-directory: install
run: |
make go-build-release
make go-build-release \
PANGOLIN_VERSION=${{ env.TAG }} \
GERBIL_VERSION=${{ env.LATEST_GERBIL_TAG }} \
BADGER_VERSION=${{ env.LATEST_BADGER_TAG }}
shell: bash
- name: Upload artifacts from /install/bin
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: install-bin
path: install/bin/
@@ -322,26 +314,100 @@ 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
- name: Copy tags from Docker Hub to GHCR
# Mirror the already-built images (all architectures) to GHCR so we can sign them
# 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 }}
echo "Waiting for multi-arch manifest to be ready..."
MAJOR_TAG=$(echo $TAG | cut -d. -f1)
MINOR_TAG=$(echo $TAG | cut -d. -f1,2)
echo "Waiting for multi-arch manifests to be ready..."
sleep 30
echo "Copying ${{ env.DOCKERHUB_IMAGE }}:${TAG} -> ${{ env.GHCR_IMAGE }}:${TAG}"
skopeo copy --all --retry-times 3 \
docker://$DOCKERHUB_IMAGE:$TAG \
docker://$GHCR_IMAGE:$TAG
# Determine if this is an RC release
IS_RC="false"
if [[ "$TAG" == *"-rc."* ]]; then
IS_RC="true"
fi
if [ "$IS_RC" = "true" ]; then
echo "RC release detected - copying version-specific tags only"
# SQLite OSS
echo "Copying ${{ env.DOCKERHUB_IMAGE }}:${TAG} -> ${{ env.GHCR_IMAGE }}:${TAG}"
skopeo copy --all --retry-times 3 \
docker://$DOCKERHUB_IMAGE:$TAG \
docker://$GHCR_IMAGE:$TAG
# PostgreSQL OSS
echo "Copying ${{ env.DOCKERHUB_IMAGE }}:postgresql-${TAG} -> ${{ env.GHCR_IMAGE }}:postgresql-${TAG}"
skopeo copy --all --retry-times 3 \
docker://$DOCKERHUB_IMAGE:postgresql-$TAG \
docker://$GHCR_IMAGE:postgresql-$TAG
# SQLite Enterprise
echo "Copying ${{ env.DOCKERHUB_IMAGE }}:ee-${TAG} -> ${{ env.GHCR_IMAGE }}:ee-${TAG}"
skopeo copy --all --retry-times 3 \
docker://$DOCKERHUB_IMAGE:ee-$TAG \
docker://$GHCR_IMAGE:ee-$TAG
# PostgreSQL Enterprise
echo "Copying ${{ env.DOCKERHUB_IMAGE }}:ee-postgresql-${TAG} -> ${{ env.GHCR_IMAGE }}:ee-postgresql-${TAG}"
skopeo copy --all --retry-times 3 \
docker://$DOCKERHUB_IMAGE:ee-postgresql-$TAG \
docker://$GHCR_IMAGE:ee-postgresql-$TAG
else
echo "Regular release detected - copying all tags (latest, major, minor, full version)"
# SQLite OSS - all tags
for TAG_SUFFIX in "latest" "$MAJOR_TAG" "$MINOR_TAG" "$TAG"; do
echo "Copying ${{ env.DOCKERHUB_IMAGE }}:${TAG_SUFFIX} -> ${{ env.GHCR_IMAGE }}:${TAG_SUFFIX}"
skopeo copy --all --retry-times 3 \
docker://$DOCKERHUB_IMAGE:$TAG_SUFFIX \
docker://$GHCR_IMAGE:$TAG_SUFFIX
done
# PostgreSQL OSS - all tags
for TAG_SUFFIX in "latest" "$MAJOR_TAG" "$MINOR_TAG" "$TAG"; do
echo "Copying ${{ env.DOCKERHUB_IMAGE }}:postgresql-${TAG_SUFFIX} -> ${{ env.GHCR_IMAGE }}:postgresql-${TAG_SUFFIX}"
skopeo copy --all --retry-times 3 \
docker://$DOCKERHUB_IMAGE:postgresql-$TAG_SUFFIX \
docker://$GHCR_IMAGE:postgresql-$TAG_SUFFIX
done
# SQLite Enterprise - all tags
for TAG_SUFFIX in "latest" "$MAJOR_TAG" "$MINOR_TAG" "$TAG"; do
echo "Copying ${{ env.DOCKERHUB_IMAGE }}:ee-${TAG_SUFFIX} -> ${{ env.GHCR_IMAGE }}:ee-${TAG_SUFFIX}"
skopeo copy --all --retry-times 3 \
docker://$DOCKERHUB_IMAGE:ee-$TAG_SUFFIX \
docker://$GHCR_IMAGE:ee-$TAG_SUFFIX
done
# PostgreSQL Enterprise - all tags
for TAG_SUFFIX in "latest" "$MAJOR_TAG" "$MINOR_TAG" "$TAG"; do
echo "Copying ${{ env.DOCKERHUB_IMAGE }}:ee-postgresql-${TAG_SUFFIX} -> ${{ env.GHCR_IMAGE }}:ee-postgresql-${TAG_SUFFIX}"
skopeo copy --all --retry-times 3 \
docker://$DOCKERHUB_IMAGE:ee-postgresql-$TAG_SUFFIX \
docker://$GHCR_IMAGE:ee-postgresql-$TAG_SUFFIX
done
fi
echo "All images copied successfully to GHCR!"
shell: bash
- name: Login to GitHub Container Registry (for cosign)
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0
with:
registry: ghcr.io
username: ${{ github.actor }}
@@ -349,7 +415,7 @@ jobs:
- name: Install cosign
# cosign is used to sign and verify container images (key and keyless)
uses: sigstore/cosign-installer@faadad0cce49287aee09b3a48701e75088a2c6ad # v4.0.0
uses: sigstore/cosign-installer@cad07c2e89fa2edd6e2d7bab4c1aa38e53f76003 # v4.1.1
- name: Dual-sign and verify (GHCR & Docker Hub)
# Sign each image by digest using keyless (OIDC) and key-based signing,
@@ -366,28 +432,127 @@ jobs:
issuer="https://token.actions.githubusercontent.com"
id_regex="^https://github.com/${{ github.repository }}/.+" # accept this repo (all workflows/refs)
for IMAGE in "${GHCR_IMAGE}" "${DOCKERHUB_IMAGE}"; do
echo "Processing ${IMAGE}:${TAG}"
# Track failures
FAILED_TAGS=()
SUCCESSFUL_TAGS=()
DIGEST="$(skopeo inspect --retry-times 3 docker://${IMAGE}:${TAG} | jq -r '.Digest')"
REF="${IMAGE}@${DIGEST}"
echo "Resolved digest: ${REF}"
# Determine if this is an RC release
IS_RC="false"
if [[ "$TAG" == *"-rc."* ]]; then
IS_RC="true"
fi
echo "==> cosign sign (keyless) --recursive ${REF}"
cosign sign --recursive "${REF}"
# Define image variants to sign
if [ "$IS_RC" = "true" ]; then
echo "RC release - signing version-specific tags only"
IMAGE_TAGS=(
"${TAG}"
"postgresql-${TAG}"
"ee-${TAG}"
"ee-postgresql-${TAG}"
)
else
echo "Regular release - signing all tags"
MAJOR_TAG=$(echo $TAG | cut -d. -f1)
MINOR_TAG=$(echo $TAG | cut -d. -f1,2)
IMAGE_TAGS=(
"latest" "$MAJOR_TAG" "$MINOR_TAG" "$TAG"
"postgresql-latest" "postgresql-$MAJOR_TAG" "postgresql-$MINOR_TAG" "postgresql-$TAG"
"ee-latest" "ee-$MAJOR_TAG" "ee-$MINOR_TAG" "ee-$TAG"
"ee-postgresql-latest" "ee-postgresql-$MAJOR_TAG" "ee-postgresql-$MINOR_TAG" "ee-postgresql-$TAG"
)
fi
echo "==> cosign sign (key) --recursive ${REF}"
cosign sign --key env://COSIGN_PRIVATE_KEY --recursive "${REF}"
# Sign each image variant for both registries
for BASE_IMAGE in "${GHCR_IMAGE}" "${DOCKERHUB_IMAGE}"; do
for IMAGE_TAG in "${IMAGE_TAGS[@]}"; do
echo "Processing ${BASE_IMAGE}:${IMAGE_TAG}"
TAG_FAILED=false
echo "==> cosign verify (public key) ${REF}"
cosign verify --key env://COSIGN_PUBLIC_KEY "${REF}" -o text
# Wrap the entire tag processing in error handling
(
set -e
DIGEST="$(skopeo inspect --retry-times 3 docker://${BASE_IMAGE}:${IMAGE_TAG} | jq -r '.Digest')"
REF="${BASE_IMAGE}@${DIGEST}"
echo "Resolved digest: ${REF}"
echo "==> cosign verify (keyless policy) ${REF}"
cosign verify \
--certificate-oidc-issuer "${issuer}" \
--certificate-identity-regexp "${id_regex}" \
"${REF}" -o text
echo "==> cosign sign (keyless) --recursive ${REF}"
cosign sign --recursive "${REF}"
echo "==> cosign sign (key) --recursive ${REF}"
cosign sign --key env://COSIGN_PRIVATE_KEY --recursive "${REF}"
# Retry wrapper for verification to handle registry propagation delays
retry_verify() {
local cmd="$1"
local attempts=6
local delay=5
local i=1
until eval "$cmd"; do
if [ $i -ge $attempts ]; then
echo "Verification failed after $attempts attempts"
return 1
fi
echo "Verification not yet available. Retry $i/$attempts after ${delay}s..."
sleep $delay
i=$((i+1))
delay=$((delay*2))
# Cap the delay to avoid very long waits
if [ $delay -gt 60 ]; then delay=60; fi
done
return 0
}
echo "==> cosign verify (public key) ${REF}"
if retry_verify "cosign verify --key env://COSIGN_PUBLIC_KEY '${REF}' -o text"; then
VERIFIED_INDEX=true
else
VERIFIED_INDEX=false
fi
echo "==> cosign verify (keyless policy) ${REF}"
if retry_verify "cosign verify --certificate-oidc-issuer '${issuer}' --certificate-identity-regexp '${id_regex}' '${REF}' -o text"; then
VERIFIED_INDEX_KEYLESS=true
else
VERIFIED_INDEX_KEYLESS=false
fi
# Check if verification succeeded
if [ "${VERIFIED_INDEX}" != "true" ] && [ "${VERIFIED_INDEX_KEYLESS}" != "true" ]; then
echo "⚠️ WARNING: Verification not available for ${BASE_IMAGE}:${IMAGE_TAG}"
echo "This may be due to registry propagation delays. Continuing anyway."
fi
) || TAG_FAILED=true
if [ "$TAG_FAILED" = "true" ]; then
echo "⚠️ WARNING: Failed to sign/verify ${BASE_IMAGE}:${IMAGE_TAG}"
FAILED_TAGS+=("${BASE_IMAGE}:${IMAGE_TAG}")
else
echo "✓ Successfully signed and verified ${BASE_IMAGE}:${IMAGE_TAG}"
SUCCESSFUL_TAGS+=("${BASE_IMAGE}:${IMAGE_TAG}")
fi
done
done
# Report summary
echo ""
echo "=========================================="
echo "Sign and Verify Summary"
echo "=========================================="
echo "Successful: ${#SUCCESSFUL_TAGS[@]}"
echo "Failed: ${#FAILED_TAGS[@]}"
echo ""
if [ ${#FAILED_TAGS[@]} -gt 0 ]; then
echo "Failed tags:"
for tag in "${FAILED_TAGS[@]}"; do
echo " - $tag"
done
echo ""
echo "⚠️ WARNING: Some tags failed to sign/verify, but continuing anyway"
else
echo "✓ All images signed and verified successfully!"
fi
shell: bash
post-run:
@@ -405,7 +570,7 @@ jobs:
permissions: write-all
steps:
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v2
uses: aws-actions/configure-aws-credentials@v6
with:
role-to-assume: arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/${{ secrets.AWS_ROLE_NAME }}
role-duration-seconds: 3600
+3 -3
View File
@@ -21,12 +21,12 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Set up Node.js
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
node-version: '22'
node-version: '24'
- name: Install dependencies
run: npm ci
+2 -2
View File
@@ -23,7 +23,7 @@ jobs:
skopeo --version
- name: Install cosign
uses: sigstore/cosign-installer@faadad0cce49287aee09b3a48701e75088a2c6ad # v4.0.0
uses: sigstore/cosign-installer@cad07c2e89fa2edd6e2d7bab4c1aa38e53f76003 # v4.1.1
- name: Input check
run: |
@@ -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
+1 -1
View File
@@ -14,7 +14,7 @@ jobs:
permissions: write-all
steps:
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v5
uses: aws-actions/configure-aws-credentials@v6
with:
role-to-assume: arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/${{ secrets.AWS_ROLE_NAME }}
role-duration-seconds: 3600
+160
View File
@@ -0,0 +1,160 @@
name: SAAS Pipeline
# CI/CD workflow for building, publishing, mirroring, signing container images and building release binaries.
# Actions are pinned to specific SHAs to reduce supply-chain risk. This workflow triggers on tag push events.
permissions:
contents: read
packages: write # for GHCR push
id-token: write # for Cosign Keyless (OIDC) Signing
on:
push:
tags:
- "[0-9]+.[0-9]+.[0-9]+-s.[0-9]+"
concurrency:
group: ${{ github.ref }}
cancel-in-progress: true
jobs:
pre-run:
runs-on: ubuntu-latest
permissions: write-all
steps:
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v6
with:
role-to-assume: arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/${{ secrets.AWS_ROLE_NAME }}
role-duration-seconds: 3600
aws-region: ${{ secrets.AWS_REGION }}
- name: Verify AWS identity
run: aws sts get-caller-identity
- name: Start EC2 instances
run: |
aws ec2 start-instances --instance-ids ${{ secrets.EC2_INSTANCE_ID_ARM_RUNNER }}
echo "EC2 instances started"
release-arm:
name: Build and Release (ARM64)
runs-on: [self-hosted, linux, arm64, us-east-1]
needs: [pre-run]
if: >-
${{
needs.pre-run.result == 'success'
}}
# Job-level timeout to avoid runaway or stuck runs
timeout-minutes: 120
env:
# Target images
AWS_IMAGE: ${{ secrets.aws_account_id }}.dkr.ecr.us-east-1.amazonaws.com/${{ github.event.repository.name }}
steps:
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Download MaxMind GeoLite2 databases
env:
MAXMIND_LICENSE_KEY: ${{ secrets.MAXMIND_LICENSE_KEY }}
run: |
echo "Downloading MaxMind GeoLite2 databases..."
# Download GeoLite2-Country
curl -L "https://download.maxmind.com/app/geoip_download?edition_id=GeoLite2-Country&license_key=${MAXMIND_LICENSE_KEY}&suffix=tar.gz" \
-o GeoLite2-Country.tar.gz
# Download GeoLite2-ASN
curl -L "https://download.maxmind.com/app/geoip_download?edition_id=GeoLite2-ASN&license_key=${MAXMIND_LICENSE_KEY}&suffix=tar.gz" \
-o GeoLite2-ASN.tar.gz
# Extract the .mmdb files
tar -xzf GeoLite2-Country.tar.gz --strip-components=1 --wildcards '*.mmdb'
tar -xzf GeoLite2-ASN.tar.gz --strip-components=1 --wildcards '*.mmdb'
# Verify files exist
if [ ! -f "GeoLite2-Country.mmdb" ]; then
echo "ERROR: Failed to download GeoLite2-Country.mmdb"
exit 1
fi
if [ ! -f "GeoLite2-ASN.mmdb" ]; then
echo "ERROR: Failed to download GeoLite2-ASN.mmdb"
exit 1
fi
# Clean up tar files
rm -f GeoLite2-Country.tar.gz GeoLite2-ASN.tar.gz
echo "MaxMind databases downloaded successfully"
ls -lh GeoLite2-*.mmdb
- name: Monitor storage space
run: |
THRESHOLD=75
USED_SPACE=$(df / | grep / | awk '{ print $5 }' | sed 's/%//g')
echo "Used space: $USED_SPACE%"
if [ "$USED_SPACE" -ge "$THRESHOLD" ]; then
echo "Used space is below the threshold of 75% free. Running Docker system prune."
echo y | docker system prune -a
else
echo "Storage space is above the threshold. No action needed."
fi
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v6
with:
role-to-assume: arn:aws:iam::${{ secrets.aws_account_id }}:role/${{ secrets.AWS_ROLE_NAME }}
role-duration-seconds: 3600
aws-region: ${{ secrets.AWS_REGION }}
- name: Login to Amazon ECR
id: login-ecr
uses: aws-actions/amazon-ecr-login@v2
- name: Extract tag name
id: get-tag
run: echo "TAG=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV
shell: bash
- name: Update version in package.json
run: |
TAG=${{ env.TAG }}
sed -i "s/export const APP_VERSION = \".*\";/export const APP_VERSION = \"$TAG\";/" server/lib/consts.ts
cat server/lib/consts.ts
shell: bash
- name: Build and push Docker images (Docker Hub - ARM64)
run: |
TAG=${{ env.TAG }}
make build-saas tag=$TAG
echo "Built & pushed ARM64 images to: ${{ env.AWS_IMAGE }}:${TAG}"
shell: bash
post-run:
needs: [pre-run, release-arm]
if: >-
${{
always() &&
needs.pre-run.result == 'success' &&
(needs.release-arm.result == 'success' || needs.release-arm.result == 'skipped' || needs.release-arm.result == 'failure')
}}
runs-on: ubuntu-latest
permissions: write-all
steps:
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v6
with:
role-to-assume: arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/${{ secrets.AWS_ROLE_NAME }}
role-duration-seconds: 3600
aws-region: ${{ secrets.AWS_REGION }}
- name: Verify AWS identity
run: aws sts get-caller-identity
- name: Stop EC2 instances
run: |
aws ec2 stop-instances --instance-ids ${{ secrets.EC2_INSTANCE_ID_ARM_RUNNER }}
echo "EC2 instances stopped"
+1 -1
View File
@@ -14,7 +14,7 @@ jobs:
stale:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@997185467fa4f803885201cee163a9f38240193d # v10.1.1
- uses: actions/stale@b5d41d4e1d5dceea10e7104786b73624c18a190f # v10.2.0
with:
days-before-stale: 14
days-before-close: 14
+7 -13
View File
@@ -14,12 +14,12 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Install Node
uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
node-version: '22'
node-version: '24'
- name: Copy config file
run: cp config/config.example.yml config/config.yml
@@ -34,10 +34,10 @@ jobs:
run: npm run set:oss
- name: Generate database migrations
run: npm run db:sqlite:generate
run: npm run db:generate
- name: Apply database migrations
run: npm run db:sqlite:push
run: npm run db:push
- name: Test with tsc
run: npx tsc --noEmit
@@ -62,10 +62,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- name: Copy config file
run: cp config/config.example.yml config/config.yml
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Build Docker image sqlite
run: make dev-build-sqlite
@@ -74,10 +71,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
- name: Copy config file
run: cp config/config.example.yml config/config.yml
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Build Docker image pg
run: make dev-build-pg
+4 -1
View File
@@ -50,4 +50,7 @@ dynamic/
*.mmdb
scratch/
tsconfig.json
hydrateSaas.ts
hydrateSaas.ts
CLAUDE.md
drizzle.config.ts
server/setup/migrations.ts
+2 -2
View File
@@ -4,7 +4,7 @@
},
"editor.defaultFormatter": "esbenp.prettier-vscode",
"[jsonc]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
"editor.defaultFormatter": "vscode.json-language-features"
},
"[javascript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
@@ -19,4 +19,4 @@
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"editor.formatOnSave": 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`);
}
+65 -44
View File
@@ -1,72 +1,93 @@
FROM node:24-alpine AS builder
# FROM node:24-slim AS base
FROM public.ecr.aws/docker/library/node:24-slim AS base
WORKDIR /app
ARG BUILD=oss
ARG DATABASE=sqlite
RUN apt-get update && apt-get install -y python3 make g++ && rm -rf /var/lib/apt/lists/*
RUN apk add --no-cache curl tzdata python3 make g++
# COPY package.json package-lock.json ./
COPY package*.json ./
FROM base AS builder-dev
RUN npm ci
COPY . .
RUN echo "export * from \"./$DATABASE\";" > server/db/index.ts
RUN echo "export const driver: \"pg\" | \"sqlite\" = \"$DATABASE\";" >> server/db/index.ts
ARG BUILD=oss
ARG DATABASE=sqlite
RUN echo "export const build = \"$BUILD\" as \"saas\" | \"enterprise\" | \"oss\";" > server/build.ts
RUN if [ "$BUILD" = "oss" ]; then rm -rf server/private; fi && \
npm run set:$DATABASE && \
npm run set:$BUILD && \
npm run db:generate && \
npm run build && \
npm run build:cli && \
test -f dist/server.mjs
# Copy the appropriate TypeScript configuration based on build type
RUN if [ "$BUILD" = "oss" ]; then cp tsconfig.oss.json tsconfig.json; \
elif [ "$BUILD" = "saas" ]; then cp tsconfig.saas.json tsconfig.json; \
elif [ "$BUILD" = "enterprise" ]; then cp tsconfig.enterprise.json tsconfig.json; \
fi
# Create placeholder files for MaxMind databases to avoid COPY errors
# Real files should be present for saas builds, placeholders for oss builds
RUN touch /app/GeoLite2-Country.mmdb /app/GeoLite2-ASN.mmdb
# if the build is oss then remove the server/private directory
RUN if [ "$BUILD" = "oss" ]; then rm -rf server/private; fi
FROM base AS builder
RUN if [ "$DATABASE" = "pg" ]; then npx drizzle-kit generate --dialect postgresql --schema ./server/db/pg/schema --out init; else npx drizzle-kit generate --dialect $DATABASE --schema ./server/db/$DATABASE/schema --out init; fi
RUN npm ci --omit=dev
RUN mkdir -p dist
RUN npm run next:build
RUN node esbuild.mjs -e server/index.ts -o dist/server.mjs -b $BUILD
RUN if [ "$DATABASE" = "pg" ]; then \
node esbuild.mjs -e server/setup/migrationsPg.ts -o dist/migrations.mjs; \
else \
node esbuild.mjs -e server/setup/migrationsSqlite.ts -o dist/migrations.mjs; \
fi
# test to make sure the build output is there and error if not
RUN test -f dist/server.mjs
RUN npm run build:cli
# Prune dev dependencies and clean up to prepare for copy to runner
RUN npm prune --omit=dev && npm cache clean --force
FROM node:24-alpine AS runner
# FROM node:24-slim AS runner
FROM public.ecr.aws/docker/library/node:24-slim AS runner
WORKDIR /app
# Only curl and tzdata needed at runtime - no build tools!
RUN apk add --no-cache curl tzdata
RUN apt-get update && apt-get install -y curl tzdata && rm -rf /var/lib/apt/lists/*
# Copy pre-built node_modules from builder (already pruned to production only)
# This includes the compiled native modules like better-sqlite3
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/init ./dist/init
COPY --from=builder /app/package.json ./package.json
COPY --from=builder-dev /app/.next/standalone ./
COPY --from=builder-dev /app/.next/static ./.next/static
COPY --from=builder-dev /app/dist ./dist
COPY --from=builder-dev /app/server/migrations ./dist/init
COPY ./cli/wrapper.sh /usr/local/bin/pangctl
RUN chmod +x /usr/local/bin/pangctl ./dist/cli.mjs
COPY server/db/names.json ./dist/names.json
COPY server/db/ios_models.json ./dist/ios_models.json
COPY server/db/mac_models.json ./dist/mac_models.json
COPY public ./public
# Copy MaxMind databases for SaaS builds
ARG BUILD=oss
RUN mkdir -p ./maxmind
# Copy MaxMind databases (placeholders exist for oss builds, real files for saas)
COPY --from=builder-dev /app/GeoLite2-Country.mmdb ./maxmind/GeoLite2-Country.mmdb
COPY --from=builder-dev /app/GeoLite2-ASN.mmdb ./maxmind/GeoLite2-ASN.mmdb
# Remove MaxMind databases for non-saas builds (keep only for saas)
RUN if [ "$BUILD" != "saas" ]; then rm -rf ./maxmind; fi
# OCI Image Labels - Build Args for dynamic values
ARG VERSION="dev"
ARG REVISION=""
ARG CREATED=""
ARG LICENSE="AGPL-3.0"
# Derive title and description based on BUILD type
ARG IMAGE_TITLE="Pangolin"
ARG IMAGE_DESCRIPTION="Identity-aware VPN and proxy for remote access to anything, anywhere"
# OCI Image Labels
# https://github.com/opencontainers/image-spec/blob/main/annotations.md
LABEL org.opencontainers.image.source="https://github.com/fosrl/pangolin" \
org.opencontainers.image.url="https://github.com/fosrl/pangolin" \
org.opencontainers.image.documentation="https://docs.pangolin.net" \
org.opencontainers.image.vendor="Fossorial" \
org.opencontainers.image.licenses="${LICENSE}" \
org.opencontainers.image.title="${IMAGE_TITLE}" \
org.opencontainers.image.description="${IMAGE_DESCRIPTION}" \
org.opencontainers.image.version="${VERSION}" \
org.opencontainers.image.revision="${REVISION}" \
org.opencontainers.image.created="${CREATED}"
CMD ["npm", "run", "start"]
+3 -1
View File
@@ -1,7 +1,9 @@
FROM node:22-alpine
FROM node:24-alpine
WORKDIR /app
RUN apk add --no-cache python3 make g++
COPY package*.json ./
# Install dependencies
+198 -7
View File
@@ -3,6 +3,25 @@
major_tag := $(shell echo $(tag) | cut -d. -f1)
minor_tag := $(shell echo $(tag) | cut -d. -f1,2)
# OCI label variables
CREATED := $(shell date -u +"%Y-%m-%dT%H:%M:%SZ")
REVISION := $(shell git rev-parse HEAD 2>/dev/null || echo "unknown")
# Common OCI build args for OSS builds
OCI_ARGS_OSS = --build-arg VERSION=$(tag) \
--build-arg REVISION=$(REVISION) \
--build-arg CREATED=$(CREATED) \
--build-arg IMAGE_TITLE="Pangolin" \
--build-arg IMAGE_DESCRIPTION="Identity-aware VPN and proxy for remote access to anything, anywhere"
# Common OCI build args for Enterprise builds
OCI_ARGS_EE = --build-arg VERSION=$(tag) \
--build-arg REVISION=$(REVISION) \
--build-arg CREATED=$(CREATED) \
--build-arg LICENSE="Fossorial Commercial" \
--build-arg IMAGE_TITLE="Pangolin EE" \
--build-arg IMAGE_DESCRIPTION="Pangolin Enterprise Edition - Identity-aware VPN and proxy for remote access to anything, anywhere"
.PHONY: build-release build-sqlite build-postgresql build-ee-sqlite build-ee-postgresql
build-release: build-sqlite build-postgresql build-ee-sqlite build-ee-postgresql
@@ -15,6 +34,7 @@ build-sqlite:
docker buildx build \
--build-arg BUILD=oss \
--build-arg DATABASE=sqlite \
$(OCI_ARGS_OSS) \
--platform linux/arm64,linux/amd64 \
--tag fosrl/pangolin:latest \
--tag fosrl/pangolin:$(major_tag) \
@@ -30,6 +50,7 @@ build-postgresql:
docker buildx build \
--build-arg BUILD=oss \
--build-arg DATABASE=pg \
$(OCI_ARGS_OSS) \
--platform linux/arm64,linux/amd64 \
--tag fosrl/pangolin:postgresql-latest \
--tag fosrl/pangolin:postgresql-$(major_tag) \
@@ -45,6 +66,7 @@ build-ee-sqlite:
docker buildx build \
--build-arg BUILD=enterprise \
--build-arg DATABASE=sqlite \
$(OCI_ARGS_EE) \
--platform linux/arm64,linux/amd64 \
--tag fosrl/pangolin:ee-latest \
--tag fosrl/pangolin:ee-$(major_tag) \
@@ -60,6 +82,7 @@ build-ee-postgresql:
docker buildx build \
--build-arg BUILD=enterprise \
--build-arg DATABASE=pg \
$(OCI_ARGS_EE) \
--platform linux/arm64,linux/amd64 \
--tag fosrl/pangolin:ee-postgresql-latest \
--tag fosrl/pangolin:ee-postgresql-$(major_tag) \
@@ -67,6 +90,18 @@ build-ee-postgresql:
--tag fosrl/pangolin:ee-postgresql-$(tag) \
--push .
build-saas:
@if [ -z "$(tag)" ]; then \
echo "Error: tag is required. Usage: make build-release tag=<tag>"; \
exit 1; \
fi
docker buildx build \
--build-arg BUILD=saas \
--build-arg DATABASE=pg \
--platform linux/arm64 \
--tag $(AWS_IMAGE):$(tag) \
--push .
build-release-arm:
@if [ -z "$(tag)" ]; then \
echo "Error: tag is required. Usage: make build-release-arm tag=<tag>"; \
@@ -74,9 +109,16 @@ build-release-arm:
fi
@MAJOR_TAG=$$(echo $(tag) | cut -d. -f1); \
MINOR_TAG=$$(echo $(tag) | cut -d. -f1,2); \
CREATED=$$(date -u +"%Y-%m-%dT%H:%M:%SZ"); \
REVISION=$$(git rev-parse HEAD 2>/dev/null || echo "unknown"); \
docker buildx build \
--build-arg BUILD=oss \
--build-arg DATABASE=sqlite \
--build-arg VERSION=$(tag) \
--build-arg REVISION=$$REVISION \
--build-arg CREATED=$$CREATED \
--build-arg IMAGE_TITLE="Pangolin" \
--build-arg IMAGE_DESCRIPTION="Identity-aware VPN and proxy for remote access to anything, anywhere" \
--platform linux/arm64 \
--tag fosrl/pangolin:latest-arm64 \
--tag fosrl/pangolin:$$MAJOR_TAG-arm64 \
@@ -86,6 +128,11 @@ build-release-arm:
docker buildx build \
--build-arg BUILD=oss \
--build-arg DATABASE=pg \
--build-arg VERSION=$(tag) \
--build-arg REVISION=$$REVISION \
--build-arg CREATED=$$CREATED \
--build-arg IMAGE_TITLE="Pangolin" \
--build-arg IMAGE_DESCRIPTION="Identity-aware VPN and proxy for remote access to anything, anywhere" \
--platform linux/arm64 \
--tag fosrl/pangolin:postgresql-latest-arm64 \
--tag fosrl/pangolin:postgresql-$$MAJOR_TAG-arm64 \
@@ -95,6 +142,12 @@ build-release-arm:
docker buildx build \
--build-arg BUILD=enterprise \
--build-arg DATABASE=sqlite \
--build-arg VERSION=$(tag) \
--build-arg REVISION=$$REVISION \
--build-arg CREATED=$$CREATED \
--build-arg LICENSE="Fossorial Commercial" \
--build-arg IMAGE_TITLE="Pangolin EE" \
--build-arg IMAGE_DESCRIPTION="Pangolin Enterprise Edition - Identity-aware VPN and proxy for remote access to anything, anywhere" \
--platform linux/arm64 \
--tag fosrl/pangolin:ee-latest-arm64 \
--tag fosrl/pangolin:ee-$$MAJOR_TAG-arm64 \
@@ -104,6 +157,12 @@ build-release-arm:
docker buildx build \
--build-arg BUILD=enterprise \
--build-arg DATABASE=pg \
--build-arg VERSION=$(tag) \
--build-arg REVISION=$$REVISION \
--build-arg CREATED=$$CREATED \
--build-arg LICENSE="Fossorial Commercial" \
--build-arg IMAGE_TITLE="Pangolin EE" \
--build-arg IMAGE_DESCRIPTION="Pangolin Enterprise Edition - Identity-aware VPN and proxy for remote access to anything, anywhere" \
--platform linux/arm64 \
--tag fosrl/pangolin:ee-postgresql-latest-arm64 \
--tag fosrl/pangolin:ee-postgresql-$$MAJOR_TAG-arm64 \
@@ -118,9 +177,16 @@ build-release-amd:
fi
@MAJOR_TAG=$$(echo $(tag) | cut -d. -f1); \
MINOR_TAG=$$(echo $(tag) | cut -d. -f1,2); \
CREATED=$$(date -u +"%Y-%m-%dT%H:%M:%SZ"); \
REVISION=$$(git rev-parse HEAD 2>/dev/null || echo "unknown"); \
docker buildx build \
--build-arg BUILD=oss \
--build-arg DATABASE=sqlite \
--build-arg VERSION=$(tag) \
--build-arg REVISION=$$REVISION \
--build-arg CREATED=$$CREATED \
--build-arg IMAGE_TITLE="Pangolin" \
--build-arg IMAGE_DESCRIPTION="Identity-aware VPN and proxy for remote access to anything, anywhere" \
--platform linux/amd64 \
--tag fosrl/pangolin:latest-amd64 \
--tag fosrl/pangolin:$$MAJOR_TAG-amd64 \
@@ -130,6 +196,11 @@ build-release-amd:
docker buildx build \
--build-arg BUILD=oss \
--build-arg DATABASE=pg \
--build-arg VERSION=$(tag) \
--build-arg REVISION=$$REVISION \
--build-arg CREATED=$$CREATED \
--build-arg IMAGE_TITLE="Pangolin" \
--build-arg IMAGE_DESCRIPTION="Identity-aware VPN and proxy for remote access to anything, anywhere" \
--platform linux/amd64 \
--tag fosrl/pangolin:postgresql-latest-amd64 \
--tag fosrl/pangolin:postgresql-$$MAJOR_TAG-amd64 \
@@ -139,6 +210,12 @@ build-release-amd:
docker buildx build \
--build-arg BUILD=enterprise \
--build-arg DATABASE=sqlite \
--build-arg VERSION=$(tag) \
--build-arg REVISION=$$REVISION \
--build-arg CREATED=$$CREATED \
--build-arg LICENSE="Fossorial Commercial" \
--build-arg IMAGE_TITLE="Pangolin EE" \
--build-arg IMAGE_DESCRIPTION="Pangolin Enterprise Edition - Identity-aware VPN and proxy for remote access to anything, anywhere" \
--platform linux/amd64 \
--tag fosrl/pangolin:ee-latest-amd64 \
--tag fosrl/pangolin:ee-$$MAJOR_TAG-amd64 \
@@ -148,6 +225,12 @@ build-release-amd:
docker buildx build \
--build-arg BUILD=enterprise \
--build-arg DATABASE=pg \
--build-arg VERSION=$(tag) \
--build-arg REVISION=$$REVISION \
--build-arg CREATED=$$CREATED \
--build-arg LICENSE="Fossorial Commercial" \
--build-arg IMAGE_TITLE="Pangolin EE" \
--build-arg IMAGE_DESCRIPTION="Pangolin Enterprise Edition - Identity-aware VPN and proxy for remote access to anything, anywhere" \
--platform linux/amd64 \
--tag fosrl/pangolin:ee-postgresql-latest-amd64 \
--tag fosrl/pangolin:ee-postgresql-$$MAJOR_TAG-amd64 \
@@ -201,27 +284,51 @@ build-rc:
echo "Error: tag is required. Usage: make build-release tag=<tag>"; \
exit 1; \
fi
@CREATED=$$(date -u +"%Y-%m-%dT%H:%M:%SZ"); \
REVISION=$$(git rev-parse HEAD 2>/dev/null || echo "unknown"); \
docker buildx build \
--build-arg BUILD=oss \
--build-arg DATABASE=sqlite \
--build-arg VERSION=$(tag) \
--build-arg REVISION=$$REVISION \
--build-arg CREATED=$$CREATED \
--build-arg IMAGE_TITLE="Pangolin" \
--build-arg IMAGE_DESCRIPTION="Identity-aware VPN and proxy for remote access to anything, anywhere" \
--platform linux/arm64,linux/amd64 \
--tag fosrl/pangolin:$(tag) \
--push .
--push . && \
docker buildx build \
--build-arg BUILD=oss \
--build-arg DATABASE=pg \
--build-arg VERSION=$(tag) \
--build-arg REVISION=$$REVISION \
--build-arg CREATED=$$CREATED \
--build-arg IMAGE_TITLE="Pangolin" \
--build-arg IMAGE_DESCRIPTION="Identity-aware VPN and proxy for remote access to anything, anywhere" \
--platform linux/arm64,linux/amd64 \
--tag fosrl/pangolin:postgresql-$(tag) \
--push .
--push . && \
docker buildx build \
--build-arg BUILD=enterprise \
--build-arg DATABASE=sqlite \
--build-arg VERSION=$(tag) \
--build-arg REVISION=$$REVISION \
--build-arg CREATED=$$CREATED \
--build-arg LICENSE="Fossorial Commercial" \
--build-arg IMAGE_TITLE="Pangolin EE" \
--build-arg IMAGE_DESCRIPTION="Pangolin Enterprise Edition - Identity-aware VPN and proxy for remote access to anything, anywhere" \
--platform linux/arm64,linux/amd64 \
--tag fosrl/pangolin:ee-$(tag) \
--push .
--push . && \
docker buildx build \
--build-arg BUILD=enterprise \
--build-arg DATABASE=pg \
--build-arg VERSION=$(tag) \
--build-arg REVISION=$$REVISION \
--build-arg CREATED=$$CREATED \
--build-arg LICENSE="Fossorial Commercial" \
--build-arg IMAGE_TITLE="Pangolin EE" \
--build-arg IMAGE_DESCRIPTION="Pangolin Enterprise Edition - Identity-aware VPN and proxy for remote access to anything, anywhere" \
--platform linux/arm64,linux/amd64 \
--tag fosrl/pangolin:ee-postgresql-$(tag) \
--push .
@@ -231,27 +338,51 @@ build-rc-arm:
echo "Error: tag is required. Usage: make build-rc-arm tag=<tag>"; \
exit 1; \
fi
@CREATED=$$(date -u +"%Y-%m-%dT%H:%M:%SZ"); \
REVISION=$$(git rev-parse HEAD 2>/dev/null || echo "unknown"); \
docker buildx build \
--build-arg BUILD=oss \
--build-arg DATABASE=sqlite \
--build-arg VERSION=$(tag) \
--build-arg REVISION=$$REVISION \
--build-arg CREATED=$$CREATED \
--build-arg IMAGE_TITLE="Pangolin" \
--build-arg IMAGE_DESCRIPTION="Identity-aware VPN and proxy for remote access to anything, anywhere" \
--platform linux/arm64 \
--tag fosrl/pangolin:$(tag)-arm64 \
--push . && \
docker buildx build \
--build-arg BUILD=oss \
--build-arg DATABASE=pg \
--build-arg VERSION=$(tag) \
--build-arg REVISION=$$REVISION \
--build-arg CREATED=$$CREATED \
--build-arg IMAGE_TITLE="Pangolin" \
--build-arg IMAGE_DESCRIPTION="Identity-aware VPN and proxy for remote access to anything, anywhere" \
--platform linux/arm64 \
--tag fosrl/pangolin:postgresql-$(tag)-arm64 \
--push . && \
docker buildx build \
--build-arg BUILD=enterprise \
--build-arg DATABASE=sqlite \
--build-arg VERSION=$(tag) \
--build-arg REVISION=$$REVISION \
--build-arg CREATED=$$CREATED \
--build-arg LICENSE="Fossorial Commercial" \
--build-arg IMAGE_TITLE="Pangolin EE" \
--build-arg IMAGE_DESCRIPTION="Pangolin Enterprise Edition - Identity-aware VPN and proxy for remote access to anything, anywhere" \
--platform linux/arm64 \
--tag fosrl/pangolin:ee-$(tag)-arm64 \
--push . && \
docker buildx build \
--build-arg BUILD=enterprise \
--build-arg DATABASE=pg \
--build-arg VERSION=$(tag) \
--build-arg REVISION=$$REVISION \
--build-arg CREATED=$$CREATED \
--build-arg LICENSE="Fossorial Commercial" \
--build-arg IMAGE_TITLE="Pangolin EE" \
--build-arg IMAGE_DESCRIPTION="Pangolin Enterprise Edition - Identity-aware VPN and proxy for remote access to anything, anywhere" \
--platform linux/arm64 \
--tag fosrl/pangolin:ee-postgresql-$(tag)-arm64 \
--push .
@@ -261,27 +392,51 @@ build-rc-amd:
echo "Error: tag is required. Usage: make build-rc-amd tag=<tag>"; \
exit 1; \
fi
@CREATED=$$(date -u +"%Y-%m-%dT%H:%M:%SZ"); \
REVISION=$$(git rev-parse HEAD 2>/dev/null || echo "unknown"); \
docker buildx build \
--build-arg BUILD=oss \
--build-arg DATABASE=sqlite \
--build-arg VERSION=$(tag) \
--build-arg REVISION=$$REVISION \
--build-arg CREATED=$$CREATED \
--build-arg IMAGE_TITLE="Pangolin" \
--build-arg IMAGE_DESCRIPTION="Identity-aware VPN and proxy for remote access to anything, anywhere" \
--platform linux/amd64 \
--tag fosrl/pangolin:$(tag)-amd64 \
--push . && \
docker buildx build \
--build-arg BUILD=oss \
--build-arg DATABASE=pg \
--build-arg VERSION=$(tag) \
--build-arg REVISION=$$REVISION \
--build-arg CREATED=$$CREATED \
--build-arg IMAGE_TITLE="Pangolin" \
--build-arg IMAGE_DESCRIPTION="Identity-aware VPN and proxy for remote access to anything, anywhere" \
--platform linux/amd64 \
--tag fosrl/pangolin:postgresql-$(tag)-amd64 \
--push . && \
docker buildx build \
--build-arg BUILD=enterprise \
--build-arg DATABASE=sqlite \
--build-arg VERSION=$(tag) \
--build-arg REVISION=$$REVISION \
--build-arg CREATED=$$CREATED \
--build-arg LICENSE="Fossorial Commercial" \
--build-arg IMAGE_TITLE="Pangolin EE" \
--build-arg IMAGE_DESCRIPTION="Pangolin Enterprise Edition - Identity-aware VPN and proxy for remote access to anything, anywhere" \
--platform linux/amd64 \
--tag fosrl/pangolin:ee-$(tag)-amd64 \
--push . && \
docker buildx build \
--build-arg BUILD=enterprise \
--build-arg DATABASE=pg \
--build-arg VERSION=$(tag) \
--build-arg REVISION=$$REVISION \
--build-arg CREATED=$$CREATED \
--build-arg LICENSE="Fossorial Commercial" \
--build-arg IMAGE_TITLE="Pangolin EE" \
--build-arg IMAGE_DESCRIPTION="Pangolin Enterprise Edition - Identity-aware VPN and proxy for remote access to anything, anywhere" \
--platform linux/amd64 \
--tag fosrl/pangolin:ee-postgresql-$(tag)-amd64 \
--push .
@@ -314,16 +469,52 @@ create-manifests-rc:
echo "All RC multi-arch manifests created successfully!"
build-arm:
docker buildx build --platform linux/arm64 -t fosrl/pangolin:latest .
@CREATED=$$(date -u +"%Y-%m-%dT%H:%M:%SZ"); \
REVISION=$$(git rev-parse HEAD 2>/dev/null || echo "unknown"); \
docker buildx build \
--build-arg VERSION=dev \
--build-arg REVISION=$$REVISION \
--build-arg CREATED=$$CREATED \
--build-arg IMAGE_TITLE="Pangolin" \
--build-arg IMAGE_DESCRIPTION="Identity-aware VPN and proxy for remote access to anything, anywhere" \
--platform linux/arm64 \
-t fosrl/pangolin:latest .
build-x86:
docker buildx build --platform linux/amd64 -t fosrl/pangolin:latest .
@CREATED=$$(date -u +"%Y-%m-%dT%H:%M:%SZ"); \
REVISION=$$(git rev-parse HEAD 2>/dev/null || echo "unknown"); \
docker buildx build \
--build-arg VERSION=dev \
--build-arg REVISION=$$REVISION \
--build-arg CREATED=$$CREATED \
--build-arg IMAGE_TITLE="Pangolin" \
--build-arg IMAGE_DESCRIPTION="Identity-aware VPN and proxy for remote access to anything, anywhere" \
--platform linux/amd64 \
-t fosrl/pangolin:latest .
dev-build-sqlite:
docker build --build-arg DATABASE=sqlite -t fosrl/pangolin:latest .
@CREATED=$$(date -u +"%Y-%m-%dT%H:%M:%SZ"); \
REVISION=$$(git rev-parse HEAD 2>/dev/null || echo "unknown"); \
docker build \
--build-arg DATABASE=sqlite \
--build-arg VERSION=dev \
--build-arg REVISION=$$REVISION \
--build-arg CREATED=$$CREATED \
--build-arg IMAGE_TITLE="Pangolin" \
--build-arg IMAGE_DESCRIPTION="Identity-aware VPN and proxy for remote access to anything, anywhere" \
-t fosrl/pangolin:latest .
dev-build-pg:
docker build --build-arg DATABASE=pg -t fosrl/pangolin:postgresql-latest .
@CREATED=$$(date -u +"%Y-%m-%dT%H:%M:%SZ"); \
REVISION=$$(git rev-parse HEAD 2>/dev/null || echo "unknown"); \
docker build \
--build-arg DATABASE=pg \
--build-arg VERSION=dev \
--build-arg REVISION=$$REVISION \
--build-arg CREATED=$$CREATED \
--build-arg IMAGE_TITLE="Pangolin" \
--build-arg IMAGE_DESCRIPTION="Identity-aware VPN and proxy for remote access to anything, anywhere" \
-t fosrl/pangolin:postgresql-latest .
test:
docker run -it -p 3000:3000 -p 3001:3001 -p 3002:3002 -v ./config:/app/config fosrl/pangolin:latest
+38 -25
View File
@@ -37,35 +37,51 @@
<p align="center">
<strong>
Start testing Pangolin at <a href="https://app.pangolin.net/auth/signup">app.pangolin.net</a>
Get started with Pangolin at <a href="https://app.pangolin.net/auth/signup">app.pangolin.net</a>
</strong>
</p>
Pangolin is an open-source, identity-based remote access platform built on WireGuard that enables secure, seamless connectivity to private and public resources. Pangolin combines reverse proxy and VPN capabilities into one platform, providing browser-based access to web applications and client-based access to any private resources, all with zero-trust security and granular access control.
Pangolin is an open-source, identity-based remote access platform built on WireGuard that enables secure, seamless connectivity to private and public resources. Pangolin combines reverse proxy and VPN capabilities into one platform, providing browser-based access to web applications and client-based access to any private resources with NAT traversal, all with granular access controls.
## Installation
- Check out the [quick install guide](https://docs.pangolin.net/self-host/quick-install) for how to install and set up Pangolin.
- Install from the [DigitalOcean marketplace](https://marketplace.digitalocean.com/apps/pangolin-ce-1?refcode=edf0480eeb81) for a one-click pre-configured installer.
- Get started for free with [Pangolin Cloud](https://app.pangolin.net/).
- Or, check out the [quick install guide](https://docs.pangolin.net/self-host/quick-install) for how to self-host Pangolin.
- Install from the [DigitalOcean marketplace](https://marketplace.digitalocean.com/apps/pangolin-ce-1?refcode=edf0480eeb81) for a one-click pre-configured installer.
<img src="public/screenshots/hero.png" />
<img src="public/screenshots/hero.png" alt="Pangolin" width="100%" />
## Deployment Options
| <img width=500 /> | Description |
|-----------------|--------------|
| **Self-Host: Community Edition** | Free, open source, and licensed under AGPL-3. |
| **Self-Host: Enterprise Edition** | Licensed under Fossorial Commercial License. Free for personal and hobbyist use, and for businesses earning under \$100K USD annually. |
| **Pangolin Cloud** | Fully managed service with instant setup and pay-as-you-go pricing — no infrastructure required. Or, self-host your own [remote node](https://docs.pangolin.net/manage/remote-node/nodes) and connect to our control plane. |
- **Pangolin Cloud** — Fully managed service - no infrastructure required.
- **Self-Host: Community Edition** — Free, open source, and licensed under AGPL-3.
- **Self-Host: Enterprise Edition** — Licensed under Fossorial Commercial License. Free for personal and hobbyist use, and for businesses making less than \$100K USD gross annual revenue.
## Key Features
| <img width=500 /> | <img width=500 /> |
|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------|
| **Connect remote networks with sites**<br /><br />Pangolin's lightweight site connectors create secure tunnels from remote networks without requiring public IP addresses or open ports. Sites make any network anywhere available for authorized access. | <img src="public/screenshots/sites.png" width=500 /><tr></tr> |
| **Browser-based reverse proxy access**<br /><br />Expose web applications through identity and context-aware tunneled reverse proxies. Pangolin handles routing, load balancing, health checking, and automatic SSL certificates without exposing your network directly to the internet. Users access applications through any web browser with authentication and granular access control. | <img src="public/clip.gif" width=500 /><tr></tr> |
| **Client-based private resource access**<br /><br />Access private resources like SSH servers, databases, RDP, and entire network ranges through Pangolin clients. Intelligent NAT traversal enables connections even through restrictive firewalls, while DNS aliases provide friendly names and fast connections to resources across all your sites. | <img src="public/screenshots/private-resources.png" width=500 /><tr></tr> |
| **Zero-trust granular access**<br /><br />Grant users access to specific resources, not entire networks. Unlike traditional VPNs that expose full network access, Pangolin's zero-trust model ensures users can only reach the applications and services you explicitly define, reducing security risk and attack surface. | <img src="public/screenshots/user-devices.png" width=500 /><tr></tr> |
### Connect remote networks with sites and NAT traversal
Pangolin's site connectors provide gateways into networks so you can access any networked resources. Sites use outbound tunnels and intelligent NAT traversal to make networks behind restrictive firewalls available for authorized access without public IPs or open ports. Easily deploy a site as a binary or container on any platform.
<img src="public/screenshots/sites.png" alt="Sites" width="100%" />
### Browser-based reverse proxy access
Expose web applications through identity and context-aware tunneled reverse proxies. Users access applications through any web browser with authentication and granular access control without installing a client. Pangolin handles routing, load balancing, health checking, and automatic SSL certificates without exposing your network directly to the internet.
<img src="public/clip.gif" alt="Reverse proxy access" width="100%" />
### Client-based private resource access
Access private resources like SSH servers, databases, RDP, and entire network ranges through Pangolin clients. Intelligent NAT traversal enables connections even through restrictive firewalls, while DNS aliases provide friendly names and fast connections to resources across all your sites. Add redundancy by routing traffic through multiple connectors in your network.
<img src="public/screenshots/private-resources.png" alt="Private resources" width="100%" />
### Give users and roles access to resources
Use Pangolin's built in users or bring your own identity provider and set up role based access control (RBAC). Grant users access to specific resources, not entire networks. Unlike traditional VPNs that expose full network access, Pangolin's zero-trust model ensures users can only reach the applications, services, and routes you explicitly define.
<img src="public/screenshots/users.png" alt="Users from identity provider with roles" width="100%" />
## Download Clients
@@ -74,20 +90,21 @@ Download the Pangolin client for your platform:
- [Mac](https://pangolin.net/downloads/mac)
- [Windows](https://pangolin.net/downloads/windows)
- [Linux](https://pangolin.net/downloads/linux)
- [iOS](https://pangolin.net/downloads/ios)
- [Android](https://pangolin.net/downloads/android)
## Get Started
### Sign up now
Create a free account at [app.pangolin.net](https://app.pangolin.net) to get started with Pangolin Cloud.
### Check out the docs
We encourage everyone to read the full documentation first, which is
available at [docs.pangolin.net](https://docs.pangolin.net). This README provides only a very brief subset of
the docs to illustrate some basic ideas.
### Sign up and try now
For Pangolin's managed service, you will first need to create an account at
[app.pangolin.net](https://app.pangolin.net). We have a generous free tier to get started.
## Licensing
Pangolin is dual licensed under the AGPL-3 and the [Fossorial Commercial License](https://pangolin.net/fcl.html). For inquiries about commercial licensing, please contact us at [contact@pangolin.net](mailto:contact@pangolin.net).
@@ -95,7 +112,3 @@ Pangolin is dual licensed under the AGPL-3 and the [Fossorial Commercial License
## Contributions
Please see [CONTRIBUTING](./CONTRIBUTING.md) in the repository for guidelines and best practices.
---
WireGuard® is a registered trademark of Jason A. Donenfeld.
+1 -1
View File
@@ -3,7 +3,7 @@
If you discover a security vulnerability, please follow the steps below to responsibly disclose it to us:
1. **Do not create a public GitHub issue or discussion post.** This could put the security of other users at risk.
2. Send a detailed report to [security@pangolin.net](mailto:security@pangolin.net) or send a **private** message to a maintainer on [Discord](https://discord.gg/HCJR8Xhme4). Include:
2. Send a detailed report to [security@pangolin.net](mailto:security@pangolin.net) with the following information:
- Description and location of the vulnerability.
- Potential impact of the vulnerability.
-72
View File
@@ -1,72 +0,0 @@
import requests
import yaml
import json
import base64
# The file path for the YAML file to be read
# You can change this to the path of your YAML file
YAML_FILE_PATH = 'blueprint.yaml'
# The API endpoint and headers from the curl request
API_URL = 'http://api.pangolin.net/v1/org/test/blueprint'
HEADERS = {
'accept': '*/*',
'Authorization': 'Bearer <your_token_here>',
'Content-Type': 'application/json'
}
def convert_and_send(file_path, url, headers):
"""
Reads a YAML file, converts its content to a JSON payload,
and sends it via a PUT request to a specified URL.
"""
try:
# Read the YAML file content
with open(file_path, 'r') as file:
yaml_content = file.read()
# Parse the YAML string to a Python dictionary
# This will be used to ensure the YAML is valid before sending
parsed_yaml = yaml.safe_load(yaml_content)
# convert the parsed YAML to a JSON string
json_payload = json.dumps(parsed_yaml)
print("Converted JSON payload:")
print(json_payload)
# Encode the JSON string to Base64
encoded_json = base64.b64encode(json_payload.encode('utf-8')).decode('utf-8')
# Create the final payload with the base64 encoded data
final_payload = {
"blueprint": encoded_json
}
print("Sending the following Base64 encoded JSON payload:")
print(final_payload)
print("-" * 20)
# Make the PUT request with the base64 encoded payload
response = requests.put(url, headers=headers, json=final_payload)
# Print the API response for debugging
print(f"API Response Status Code: {response.status_code}")
print("API Response Content:")
print(response.text)
# Raise an exception for bad status codes (4xx or 5xx)
response.raise_for_status()
except FileNotFoundError:
print(f"Error: The file '{file_path}' was not found.")
except yaml.YAMLError as e:
print(f"Error parsing YAML file: {e}")
except requests.exceptions.RequestException as e:
print(f"An error occurred during the API request: {e}")
except Exception as e:
print(f"An unexpected error occurred: {e}")
# Run the function
if __name__ == "__main__":
convert_and_send(YAML_FILE_PATH, API_URL, HEADERS)
-70
View File
@@ -1,70 +0,0 @@
client-resources:
client-resource-nice-id-uno:
name: this is my resource
protocol: tcp
proxy-port: 3001
hostname: localhost
internal-port: 3000
site: lively-yosemite-toad
client-resource-nice-id-duce:
name: this is my resource
protocol: udp
proxy-port: 3000
hostname: localhost
internal-port: 3000
site: lively-yosemite-toad
proxy-resources:
resource-nice-id-uno:
name: this is my resource
protocol: http
full-domain: duce.test.example.com
host-header: example.com
tls-server-name: example.com
# auth:
# pincode: 123456
# password: sadfasdfadsf
# sso-enabled: true
# sso-roles:
# - Member
# sso-users:
# - owen@pangolin.net
# whitelist-users:
# - owen@pangolin.net
# auto-login-idp: 1
headers:
- name: X-Example-Header
value: example-value
- name: X-Another-Header
value: another-value
rules:
- action: allow
match: ip
value: 1.1.1.1
- action: deny
match: cidr
value: 2.2.2.2/32
- action: pass
match: path
value: /admin
targets:
- site: lively-yosemite-toad
path: /path
pathMatchType: prefix
hostname: localhost
method: http
port: 8000
- site: slim-alpine-chipmunk
hostname: localhost
path: /yoman
pathMatchType: exact
method: http
port: 8001
resource-nice-id-duce:
name: this is other resource
protocol: tcp
proxy-port: 3000
targets:
- site: lively-yosemite-toad
hostname: localhost
port: 3000
+36
View File
@@ -0,0 +1,36 @@
import { CommandModule } from "yargs";
import { db, licenseKey } from "@server/db";
import { eq } from "drizzle-orm";
type ClearLicenseKeysArgs = { };
export const clearLicenseKeys: CommandModule<
{},
ClearLicenseKeysArgs
> = {
command: "clear-license-keys",
describe:
"Clear all license keys from the database",
// no args
builder: (yargs) => {
return yargs;
},
handler: async (argv: {}) => {
try {
console.log(`Clearing all license keys from the database`);
// Delete all license keys
const deletedCount = await db
.delete(licenseKey)
.where(eq(licenseKey.licenseKeyId, licenseKey.licenseKeyId)) .returning();; // delete all
console.log(`Deleted ${deletedCount.length} license key(s) from the database`);
process.exit(0);
} catch (error) {
console.error("Error:", error);
process.exit(1);
}
}
};
+123
View File
@@ -0,0 +1,123 @@
import { CommandModule } from "yargs";
import { db, clients, olms, currentFingerprint, userClients, approvals } from "@server/db";
import { eq, and, inArray } from "drizzle-orm";
type DeleteClientArgs = {
orgId: string;
niceId: string;
};
export const deleteClient: CommandModule<{}, DeleteClientArgs> = {
command: "delete-client",
describe:
"Delete a client and all associated data (OLMs, current fingerprint, userClients, approvals). Snapshots are preserved.",
builder: (yargs) => {
return yargs
.option("orgId", {
type: "string",
demandOption: true,
describe: "The organization ID"
})
.option("niceId", {
type: "string",
demandOption: true,
describe: "The client niceId (identifier)"
});
},
handler: async (argv: { orgId: string; niceId: string }) => {
try {
const { orgId, niceId } = argv;
console.log(
`Deleting client with orgId: ${orgId}, niceId: ${niceId}...`
);
// Find the client
const [client] = await db
.select()
.from(clients)
.where(and(eq(clients.orgId, orgId), eq(clients.niceId, niceId)))
.limit(1);
if (!client) {
console.error(
`Error: Client with orgId "${orgId}" and niceId "${niceId}" not found.`
);
process.exit(1);
}
const clientId = client.clientId;
console.log(`Found client with clientId: ${clientId}`);
// Find all OLMs associated with this client
const associatedOlms = await db
.select()
.from(olms)
.where(eq(olms.clientId, clientId));
console.log(`Found ${associatedOlms.length} OLM(s) associated with this client`);
// Delete in a transaction to ensure atomicity
await db.transaction(async (trx) => {
// Delete currentFingerprint entries for the associated OLMs
// Note: We delete these explicitly before deleting OLMs to ensure
// we have control, even though cascade would handle it
let fingerprintCount = 0;
if (associatedOlms.length > 0) {
const olmIds = associatedOlms.map((olm) => olm.olmId);
const deletedFingerprints = await trx
.delete(currentFingerprint)
.where(inArray(currentFingerprint.olmId, olmIds))
.returning();
fingerprintCount = deletedFingerprints.length;
}
console.log(`Deleted ${fingerprintCount} current fingerprint(s)`);
// Delete OLMs
// Note: OLMs have onDelete: "set null" for clientId, so we need to delete them explicitly
const deletedOlms = await trx
.delete(olms)
.where(eq(olms.clientId, clientId))
.returning();
console.log(`Deleted ${deletedOlms.length} OLM(s)`);
// Delete approvals
// Note: Approvals have onDelete: "cascade" but we delete explicitly for clarity
const deletedApprovals = await trx
.delete(approvals)
.where(eq(approvals.clientId, clientId))
.returning();
console.log(`Deleted ${deletedApprovals.length} approval(s)`);
// Delete userClients
// Note: userClients have onDelete: "cascade" but we delete explicitly for clarity
const deletedUserClients = await trx
.delete(userClients)
.where(eq(userClients.clientId, clientId))
.returning();
console.log(`Deleted ${deletedUserClients.length} userClient association(s)`);
// Finally, delete the client itself
const deletedClients = await trx
.delete(clients)
.where(eq(clients.clientId, clientId))
.returning();
console.log(`Deleted client: ${deletedClients[0]?.name || niceId}`);
});
console.log("\nClient deletion completed successfully!");
console.log("\nSummary:");
console.log(` - Client: ${niceId} (clientId: ${clientId})`);
console.log(` - Olm(s): ${associatedOlms.length}`);
console.log(` - Current fingerprints: deleted`);
console.log(` - Approvals: deleted`);
console.log(` - UserClients: deleted`);
console.log(` - Snapshots: preserved (not deleted)`);
process.exit(0);
} catch (error) {
console.error("Error deleting client:", error);
process.exit(1);
}
}
};
+121
View File
@@ -0,0 +1,121 @@
import { CommandModule } from "yargs";
import { db, orgs } from "@server/db";
import { eq } from "drizzle-orm";
import { encrypt } from "@server/lib/crypto";
import { configFilePath1, configFilePath2 } from "@server/lib/consts";
import { generateCA } from "@server/lib/sshCA";
import fs from "fs";
import yaml from "js-yaml";
type GenerateOrgCaKeysArgs = {
orgId: string;
secret?: string;
force?: boolean;
};
export const generateOrgCaKeys: CommandModule<{}, GenerateOrgCaKeysArgs> = {
command: "generate-org-ca-keys",
describe:
"Generate SSH CA public/private key pair for an organization and store them in the database (private key encrypted with server secret)",
builder: (yargs) => {
return yargs
.option("orgId", {
type: "string",
demandOption: true,
describe: "The organization ID"
})
.option("secret", {
type: "string",
describe:
"Server secret used to encrypt the CA private key. If omitted, read from config file (config.yml or config.yaml)."
})
.option("force", {
type: "boolean",
default: false,
describe:
"Overwrite existing CA keys for the org if they already exist"
});
},
handler: async (argv: {
orgId: string;
secret?: string;
force?: boolean;
}) => {
try {
const { orgId, force } = argv;
let secret = argv.secret;
if (!secret) {
const configPath = fs.existsSync(configFilePath1)
? configFilePath1
: fs.existsSync(configFilePath2)
? configFilePath2
: null;
if (!configPath) {
console.error(
"Error: No server secret provided and config file not found. " +
"Expected config.yml or config.yaml in the config directory, or pass --secret."
);
process.exit(1);
}
const configContent = fs.readFileSync(configPath, "utf8");
const config = yaml.load(configContent) as {
server?: { secret?: string };
};
if (!config?.server?.secret) {
console.error(
"Error: No server.secret in config file. Pass --secret or set server.secret in config."
);
process.exit(1);
}
secret = config.server.secret;
}
const [org] = await db
.select({
orgId: orgs.orgId,
sshCaPrivateKey: orgs.sshCaPrivateKey,
sshCaPublicKey: orgs.sshCaPublicKey
})
.from(orgs)
.where(eq(orgs.orgId, orgId))
.limit(1);
if (!org) {
console.error(`Error: Organization with orgId "${orgId}" not found.`);
process.exit(1);
}
if (org.sshCaPrivateKey != null || org.sshCaPublicKey != null) {
if (!force) {
console.error(
"Error: This organization already has CA keys. Use --force to overwrite."
);
process.exit(1);
}
}
const ca = generateCA(`pangolin-ssh-ca-${orgId}`);
const encryptedPrivateKey = encrypt(ca.privateKeyPem, secret);
await db
.update(orgs)
.set({
sshCaPrivateKey: encryptedPrivateKey,
sshCaPublicKey: ca.publicKeyOpenSSH
})
.where(eq(orgs.orgId, orgId));
console.log("SSH CA keys generated and stored for org:", orgId);
console.log("\nPublic key (OpenSSH format):");
console.log(ca.publicKeyOpenSSH);
process.exit(0);
} catch (error) {
console.error("Error generating org CA keys:", error);
process.exit(1);
}
}
};
+6
View File
@@ -6,6 +6,9 @@ import { setAdminCredentials } from "@cli/commands/setAdminCredentials";
import { resetUserSecurityKeys } from "@cli/commands/resetUserSecurityKeys";
import { clearExitNodes } from "./commands/clearExitNodes";
import { rotateServerSecret } from "./commands/rotateServerSecret";
import { clearLicenseKeys } from "./commands/clearLicenseKeys";
import { deleteClient } from "./commands/deleteClient";
import { generateOrgCaKeys } from "./commands/generateOrgCaKeys";
yargs(hideBin(process.argv))
.scriptName("pangctl")
@@ -13,5 +16,8 @@ yargs(hideBin(process.argv))
.command(resetUserSecurityKeys)
.command(clearExitNodes)
.command(rotateServerSecret)
.command(clearLicenseKeys)
.command(deleteClient)
.command(generateOrgCaKeys)
.demandCommand()
.help().argv;
+24 -21
View File
@@ -1,27 +1,30 @@
# To see all available options, please visit the docs:
# https://docs.pangolin.net/self-host/advanced/config-file
app:
dashboard_url: http://localhost:3002
log_level: debug
domains:
domain1:
base_domain: example.com
server:
secret: my_secret_key
# https://docs.pangolin.net/
gerbil:
base_endpoint: example.com
start_port: 51820
base_endpoint: "{{.DashboardDomain}}"
orgs:
block_size: 24
subnet_group: 100.90.137.0/20
app:
dashboard_url: "https://{{.DashboardDomain}}"
log_level: "info"
telemetry:
anonymous_usage: true
domains:
domain1:
base_domain: "{{.BaseDomain}}"
server:
secret: "{{.Secret}}"
cors:
origins: ["https://{{.DashboardDomain}}"]
methods: ["GET", "POST", "PUT", "DELETE", "PATCH"]
allowed_headers: ["X-CSRF-Token", "Content-Type"]
credentials: false
flags:
require_email_verification: false
disable_signup_without_invite: true
disable_user_create_org: true
allow_raw_resources: true
enable_integration_api: true
require_email_verification: false
disable_signup_without_invite: true
disable_user_create_org: false
allow_raw_resources: true
+1
View File
@@ -0,0 +1 @@
*-journal
+19 -3
View File
@@ -1,5 +1,9 @@
http:
middlewares:
badger:
plugin:
badger:
disableForwardAuth: true
redirect-to-https:
redirectScheme:
scheme: https
@@ -13,14 +17,16 @@ http:
- web
middlewares:
- redirect-to-https
- badger
# Next.js router (handles everything except API and WebSocket paths)
next-router:
rule: "Host(`{{.DashboardDomain}}`)"
rule: "Host(`{{.DashboardDomain}}`) && !PathPrefix(`/api/v1`)"
service: next-service
priority: 10
entryPoints:
- websecure
middlewares:
- badger
tls:
certResolver: letsencrypt
@@ -28,9 +34,10 @@ http:
api-router:
rule: "Host(`{{.DashboardDomain}}`) && PathPrefix(`/api/v1`)"
service: api-service
priority: 100
entryPoints:
- websecure
middlewares:
- badger
tls:
certResolver: letsencrypt
@@ -44,3 +51,12 @@ http:
loadBalancer:
servers:
- url: "http://pangolin:3000" # API/WebSocket server
tcp:
serversTransports:
pp-transport-v1:
proxyProtocol:
version: 1
pp-transport-v2:
proxyProtocol:
version: 2
+25 -5
View File
@@ -3,32 +3,52 @@ api:
dashboard: true
providers:
http:
endpoint: "http://pangolin:3001/api/v1/traefik-config"
pollInterval: "5s"
file:
directory: "/var/dynamic"
watch: true
filename: "/etc/traefik/dynamic_config.yml"
experimental:
plugins:
badger:
moduleName: "github.com/fosrl/badger"
version: "v1.3.0"
version: "{{.BadgerVersion}}"
log:
level: "DEBUG"
level: "INFO"
format: "common"
maxSize: 100
maxBackups: 3
maxAge: 3
compress: true
certificatesResolvers:
letsencrypt:
acme:
httpChallenge:
entryPoint: web
email: "{{.LetsEncryptEmail}}"
storage: "/letsencrypt/acme.json"
caServer: "https://acme-v02.api.letsencrypt.org/directory"
entryPoints:
web:
address: ":80"
websecure:
address: ":9443"
address: ":443"
transport:
respondingTimeouts:
readTimeout: "30m"
http:
tls:
certResolver: "letsencrypt"
encodedCharacters:
allowEncodedSlash: true
allowEncodedQuestionMark: true
serversTransport:
insecureSkipVerify: true
ping:
entryPoint: "web"
+6
View File
@@ -4,6 +4,12 @@ services:
image: fosrl/pangolin:latest
container_name: pangolin
restart: unless-stopped
deploy:
resources:
limits:
memory: 1g
reservations:
memory: 256m
volumes:
- ./config:/app/config
healthcheck:
+2 -2
View File
@@ -7,8 +7,8 @@ services:
POSTGRES_DB: postgres # Default database name
POSTGRES_USER: postgres # Default user
POSTGRES_PASSWORD: password # Default password (change for production!)
volumes:
- ./config/postgres:/var/lib/postgresql/data
# volumes:
# - ./config/postgres:/var/lib/postgresql/data
ports:
- "5432:5432" # Map host port 5432 to container port 5432
restart: no
+8 -2
View File
@@ -6,6 +6,12 @@ import path from "path";
import fs from "fs";
// import { glob } from "glob";
// Read default build type from server/build.ts
let build = "oss";
const buildFile = fs.readFileSync(path.resolve("server/build.ts"), "utf8");
const m = buildFile.match(/export\s+const\s+build\s*=\s*["'](oss|saas|enterprise)["']/);
if (m) build = m[1];
const banner = `
// patch __dirname
// import { fileURLToPath } from "url";
@@ -37,7 +43,7 @@ const argv = yargs(hideBin(process.argv))
describe: "Build type (oss, saas, enterprise)",
type: "string",
choices: ["oss", "saas", "enterprise"],
default: "oss"
default: build
})
.help()
.alias("help", "h").argv;
@@ -275,7 +281,7 @@ esbuild
})
],
sourcemap: "inline",
target: "node22"
target: "node24"
})
.then((result) => {
// Check if there were any errors in the build result
+17 -34
View File
@@ -1,41 +1,24 @@
all: update-versions go-build-release put-back
dev-all: dev-update-versions dev-build dev-clean
all: go-build-release
# Build with version injection via ldflags
# Versions can be passed via: make go-build-release PANGOLIN_VERSION=x.x.x GERBIL_VERSION=x.x.x BADGER_VERSION=x.x.x
# Or fetched automatically if not provided (requires curl and jq)
PANGOLIN_VERSION ?= $(shell curl -s https://api.github.com/repos/fosrl/pangolin/tags | jq -r '.[0].name')
GERBIL_VERSION ?= $(shell curl -s https://api.github.com/repos/fosrl/gerbil/tags | jq -r '.[0].name')
BADGER_VERSION ?= $(shell curl -s https://api.github.com/repos/fosrl/badger/tags | jq -r '.[0].name')
LDFLAGS = -X main.pangolinVersion=$(PANGOLIN_VERSION) \
-X main.gerbilVersion=$(GERBIL_VERSION) \
-X main.badgerVersion=$(BADGER_VERSION)
go-build-release:
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o bin/installer_linux_amd64
CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -o bin/installer_linux_arm64
@echo "Building with versions - Pangolin: $(PANGOLIN_VERSION), Gerbil: $(GERBIL_VERSION), Badger: $(BADGER_VERSION)"
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags "$(LDFLAGS)" -o bin/installer_linux_amd64
CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -ldflags "$(LDFLAGS)" -o bin/installer_linux_arm64
clean:
rm -f bin/installer_linux_amd64
rm -f bin/installer_linux_arm64
update-versions:
@echo "Fetching latest versions..."
cp main.go main.go.bak && \
$(MAKE) dev-update-versions
put-back:
mv main.go.bak main.go
dev-update-versions:
if [ -z "$(tag)" ]; then \
PANGOLIN_VERSION=$$(curl -s https://api.github.com/repos/fosrl/pangolin/tags | jq -r '.[0].name'); \
else \
PANGOLIN_VERSION=$(tag); \
fi && \
GERBIL_VERSION=$$(curl -s https://api.github.com/repos/fosrl/gerbil/tags | jq -r '.[0].name') && \
BADGER_VERSION=$$(curl -s https://api.github.com/repos/fosrl/badger/tags | jq -r '.[0].name') && \
echo "Latest versions - Pangolin: $$PANGOLIN_VERSION, Gerbil: $$GERBIL_VERSION, Badger: $$BADGER_VERSION" && \
sed -i "s/config.PangolinVersion = \".*\"/config.PangolinVersion = \"$$PANGOLIN_VERSION\"/" main.go && \
sed -i "s/config.GerbilVersion = \".*\"/config.GerbilVersion = \"$$GERBIL_VERSION\"/" main.go && \
sed -i "s/config.BadgerVersion = \".*\"/config.BadgerVersion = \"$$BADGER_VERSION\"/" main.go && \
echo "Updated main.go with latest versions"
dev-build: go-build-release
dev-clean:
@echo "Restoring version values ..."
sed -i "s/config.PangolinVersion = \".*\"/config.PangolinVersion = \"replaceme\"/" main.go && \
sed -i "s/config.GerbilVersion = \".*\"/config.GerbilVersion = \"replaceme\"/" main.go && \
sed -i "s/config.BadgerVersion = \".*\"/config.BadgerVersion = \"replaceme\"/" main.go
@echo "Restored version strings in main.go"
.PHONY: all go-build-release clean
+25 -26
View File
@@ -99,11 +99,6 @@ func ReadAppConfig(configPath string) (*AppConfigValues, error) {
return values, nil
}
// findPattern finds the start of a pattern in a string
func findPattern(s, pattern string) int {
return bytes.Index([]byte(s), []byte(pattern))
}
func copyDockerService(sourceFile, destFile, serviceName string) error {
// Read source file
sourceData, err := os.ReadFile(sourceFile)
@@ -118,19 +113,19 @@ func copyDockerService(sourceFile, destFile, serviceName string) error {
}
// Parse source Docker Compose YAML
var sourceCompose map[string]interface{}
var sourceCompose map[string]any
if err := yaml.Unmarshal(sourceData, &sourceCompose); err != nil {
return fmt.Errorf("error parsing source Docker Compose file: %w", err)
}
// Parse destination Docker Compose YAML
var destCompose map[string]interface{}
var destCompose map[string]any
if err := yaml.Unmarshal(destData, &destCompose); err != nil {
return fmt.Errorf("error parsing destination Docker Compose file: %w", err)
}
// Get services section from source
sourceServices, ok := sourceCompose["services"].(map[string]interface{})
sourceServices, ok := sourceCompose["services"].(map[string]any)
if !ok {
return fmt.Errorf("services section not found in source file or has invalid format")
}
@@ -142,10 +137,10 @@ func copyDockerService(sourceFile, destFile, serviceName string) error {
}
// Get or create services section in destination
destServices, ok := destCompose["services"].(map[string]interface{})
destServices, ok := destCompose["services"].(map[string]any)
if !ok {
// If services section doesn't exist, create it
destServices = make(map[string]interface{})
destServices = make(map[string]any)
destCompose["services"] = destServices
}
@@ -187,17 +182,21 @@ func backupConfig() error {
return nil
}
func MarshalYAMLWithIndent(data interface{}, indent int) ([]byte, error) {
func MarshalYAMLWithIndent(data any, indent int) (resp []byte, err error) {
buffer := new(bytes.Buffer)
encoder := yaml.NewEncoder(buffer)
encoder.SetIndent(indent)
err := encoder.Encode(data)
if err != nil {
if err := encoder.Encode(data); err != nil {
return nil, err
}
defer encoder.Close()
defer func() {
if cerr := encoder.Close(); cerr != nil && err == nil {
err = cerr
}
}()
return buffer.Bytes(), nil
}
@@ -209,7 +208,7 @@ func replaceInFile(filepath, oldStr, newStr string) error {
}
// Replace the string
newContent := strings.Replace(string(content), oldStr, newStr, -1)
newContent := strings.ReplaceAll(string(content), oldStr, newStr)
// Write the modified content back to the file
err = os.WriteFile(filepath, []byte(newContent), 0644)
@@ -228,28 +227,28 @@ func CheckAndAddTraefikLogVolume(composePath string) error {
}
// Parse YAML into a generic map
var compose map[string]interface{}
var compose map[string]any
if err := yaml.Unmarshal(data, &compose); err != nil {
return fmt.Errorf("error parsing compose file: %w", err)
}
// Get services section
services, ok := compose["services"].(map[string]interface{})
services, ok := compose["services"].(map[string]any)
if !ok {
return fmt.Errorf("services section not found or invalid")
}
// Get traefik service
traefik, ok := services["traefik"].(map[string]interface{})
traefik, ok := services["traefik"].(map[string]any)
if !ok {
return fmt.Errorf("traefik service not found or invalid")
}
// Check volumes
logVolume := "./config/traefik/logs:/var/log/traefik"
var volumes []interface{}
var volumes []any
if existingVolumes, ok := traefik["volumes"].([]interface{}); ok {
if existingVolumes, ok := traefik["volumes"].([]any); ok {
// Check if volume already exists
for _, v := range existingVolumes {
if v.(string) == logVolume {
@@ -295,13 +294,13 @@ func MergeYAML(baseFile, overlayFile string) error {
}
// Parse base YAML into a map
var baseMap map[string]interface{}
var baseMap map[string]any
if err := yaml.Unmarshal(baseContent, &baseMap); err != nil {
return fmt.Errorf("error parsing base YAML: %v", err)
}
// Parse overlay YAML into a map
var overlayMap map[string]interface{}
var overlayMap map[string]any
if err := yaml.Unmarshal(overlayContent, &overlayMap); err != nil {
return fmt.Errorf("error parsing overlay YAML: %v", err)
}
@@ -324,8 +323,8 @@ func MergeYAML(baseFile, overlayFile string) error {
}
// mergeMap recursively merges two maps
func mergeMap(base, overlay map[string]interface{}) map[string]interface{} {
result := make(map[string]interface{})
func mergeMap(base, overlay map[string]any) map[string]any {
result := make(map[string]any)
// Copy all key-values from base map
for k, v := range base {
@@ -336,8 +335,8 @@ func mergeMap(base, overlay map[string]interface{}) map[string]interface{} {
for k, v := range overlay {
// If both maps have the same key and both values are maps, merge recursively
if baseVal, ok := base[k]; ok {
if baseMap, isBaseMap := baseVal.(map[string]interface{}); isBaseMap {
if overlayMap, isOverlayMap := v.(map[string]interface{}); isOverlayMap {
if baseMap, isBaseMap := baseVal.(map[string]any); isBaseMap {
if overlayMap, isOverlayMap := v.(map[string]any); isOverlayMap {
result[k] = mergeMap(baseMap, overlayMap)
continue
}
+10 -2
View File
@@ -81,11 +81,19 @@ entryPoints:
transport:
respondingTimeouts:
readTimeout: "30m"
http3:
advertisedPort: 443
http:
tls:
certResolver: "letsencrypt"
middlewares:
middlewares:
- crowdsec@file
encodedCharacters:
allowEncodedSlash: true
allowEncodedQuestionMark: true
serversTransport:
insecureSkipVerify: true
insecureSkipVerify: true
ping:
entryPoint: "web"
+8 -3
View File
@@ -4,6 +4,12 @@ services:
image: docker.io/fosrl/pangolin:{{if .IsEnterprise}}ee-{{end}}{{.PangolinVersion}}
container_name: pangolin
restart: unless-stopped
deploy:
resources:
limits:
memory: 1g
reservations:
memory: 256m
volumes:
- ./config:/app/config
healthcheck:
@@ -32,15 +38,14 @@ services:
- 51820:51820/udp
- 21820:21820/udp
- 443:443
- 443:443/udp # For http3 QUIC if desired
- 80:80
{{end}}
traefik:
image: docker.io/traefik:v3.6
container_name: traefik
restart: unless-stopped
{{if .InstallGerbil}}
network_mode: service:gerbil # Ports appear on the gerbil service
{{end}}{{if not .InstallGerbil}}
{{if .InstallGerbil}} network_mode: service:gerbil # Ports appear on the gerbil service{{end}}{{if not .InstallGerbil}}
ports:
- 443:443
- 80:80
+6 -1
View File
@@ -40,12 +40,17 @@ entryPoints:
transport:
respondingTimeouts:
readTimeout: "30m"
http3:
advertisedPort: 443
http:
tls:
certResolver: "letsencrypt"
encodedCharacters:
allowEncodedSlash: true
allowEncodedQuestionMark: true
serversTransport:
insecureSkipVerify: true
ping:
entryPoint: "web"
entryPoint: "web"
+48 -6
View File
@@ -144,12 +144,13 @@ func installDocker() error {
}
func startDockerService() error {
if runtime.GOOS == "linux" {
switch runtime.GOOS {
case "linux":
cmd := exec.Command("systemctl", "enable", "--now", "docker")
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
return cmd.Run()
} else if runtime.GOOS == "darwin" {
case "darwin":
// On macOS, Docker is usually started via the Docker Desktop application
fmt.Println("Please start Docker Desktop manually on macOS.")
return nil
@@ -210,6 +211,47 @@ func isDockerRunning() bool {
return true
}
func isPodmanRunning() bool {
cmd := exec.Command("podman", "info")
if err := cmd.Run(); err != nil {
return false
}
return true
}
// detectContainerType detects whether the system is currently using Docker or Podman
// by checking which container runtime is running and has containers
func detectContainerType() SupportedContainer {
// Check if we have running containers with podman
if isPodmanRunning() {
cmd := exec.Command("podman", "ps", "-q")
output, err := cmd.Output()
if err == nil && len(strings.TrimSpace(string(output))) > 0 {
return Podman
}
}
// Check if we have running containers with docker
if isDockerRunning() {
cmd := exec.Command("docker", "ps", "-q")
output, err := cmd.Output()
if err == nil && len(strings.TrimSpace(string(output))) > 0 {
return Docker
}
}
// If no containers are running, check which one is installed and running
if isPodmanRunning() && isPodmanInstalled() {
return Podman
}
if isDockerRunning() && isDockerInstalled() {
return Docker
}
return Undefined
}
// executeDockerComposeCommandWithArgs executes the appropriate docker command with arguments supplied
func executeDockerComposeCommandWithArgs(args ...string) error {
var cmd *exec.Cmd
@@ -261,7 +303,7 @@ func pullContainers(containerType SupportedContainer) error {
return nil
}
return fmt.Errorf("Unsupported container type: %s", containerType)
return fmt.Errorf("unsupported container type: %s", containerType)
}
// startContainers starts the containers using the appropriate command.
@@ -284,7 +326,7 @@ func startContainers(containerType SupportedContainer) error {
return nil
}
return fmt.Errorf("Unsupported container type: %s", containerType)
return fmt.Errorf("unsupported container type: %s", containerType)
}
// stopContainers stops the containers using the appropriate command.
@@ -306,7 +348,7 @@ func stopContainers(containerType SupportedContainer) error {
return nil
}
return fmt.Errorf("Unsupported container type: %s", containerType)
return fmt.Errorf("unsupported container type: %s", containerType)
}
// restartContainer restarts a specific container using the appropriate command.
@@ -328,5 +370,5 @@ func restartContainer(container string, containerType SupportedContainer) error
return nil
}
return fmt.Errorf("Unsupported container type: %s", containerType)
return fmt.Errorf("unsupported container type: %s", containerType)
}
+21 -12
View File
@@ -27,9 +27,18 @@ func installCrowdsec(config Config) error {
os.Exit(1)
}
os.MkdirAll("config/crowdsec/db", 0755)
os.MkdirAll("config/crowdsec/acquis.d", 0755)
os.MkdirAll("config/traefik/logs", 0755)
if err := os.MkdirAll("config/crowdsec/db", 0755); err != nil {
fmt.Printf("Error creating config files: %v\n", err)
os.Exit(1)
}
if err := os.MkdirAll("config/crowdsec/acquis.d", 0755); err != nil {
fmt.Printf("Error creating config files: %v\n", err)
os.Exit(1)
}
if err := os.MkdirAll("config/traefik/logs", 0755); err != nil {
fmt.Printf("Error creating config files: %v\n", err)
os.Exit(1)
}
if err := copyDockerService("config/crowdsec/docker-compose.yml", "docker-compose.yml", "crowdsec"); err != nil {
fmt.Printf("Error copying docker service: %v\n", err)
@@ -93,7 +102,7 @@ func installCrowdsec(config Config) error {
if checkIfTextInFile("config/traefik/dynamic_config.yml", "PUT_YOUR_BOUNCER_KEY_HERE_OR_IT_WILL_NOT_WORK") {
fmt.Println("Failed to replace bouncer key! Please retrieve the key and replace it in the config/traefik/dynamic_config.yml file using the following command:")
fmt.Println(" docker exec crowdsec cscli bouncers add traefik-bouncer")
fmt.Printf(" %s exec crowdsec cscli bouncers add traefik-bouncer\n", config.InstallationContainerType)
}
return nil
@@ -117,7 +126,7 @@ func GetCrowdSecAPIKey(containerType SupportedContainer) (string, error) {
}
// Execute the command to get the API key
cmd := exec.Command("docker", "exec", "crowdsec", "cscli", "bouncers", "add", "traefik-bouncer", "-o", "raw")
cmd := exec.Command(string(containerType), "exec", "crowdsec", "cscli", "bouncers", "add", "traefik-bouncer", "-o", "raw")
var out bytes.Buffer
cmd.Stdout = &out
@@ -153,34 +162,34 @@ func CheckAndAddCrowdsecDependency(composePath string) error {
}
// Parse YAML into a generic map
var compose map[string]interface{}
var compose map[string]any
if err := yaml.Unmarshal(data, &compose); err != nil {
return fmt.Errorf("error parsing compose file: %w", err)
}
// Get services section
services, ok := compose["services"].(map[string]interface{})
services, ok := compose["services"].(map[string]any)
if !ok {
return fmt.Errorf("services section not found or invalid")
}
// Get traefik service
traefik, ok := services["traefik"].(map[string]interface{})
traefik, ok := services["traefik"].(map[string]any)
if !ok {
return fmt.Errorf("traefik service not found or invalid")
}
// Get dependencies
dependsOn, ok := traefik["depends_on"].(map[string]interface{})
dependsOn, ok := traefik["depends_on"].(map[string]any)
if ok {
// Append the new block for crowdsec
dependsOn["crowdsec"] = map[string]interface{}{
dependsOn["crowdsec"] = map[string]any{
"condition": "service_healthy",
}
} else {
// No dependencies exist, create it
traefik["depends_on"] = map[string]interface{}{
"crowdsec": map[string]interface{}{
traefik["depends_on"] = map[string]any{
"crowdsec": map[string]any{
"condition": "service_healthy",
},
}
+31 -3
View File
@@ -1,10 +1,38 @@
module installer
go 1.24.0
go 1.25.0
require (
golang.org/x/term v0.38.0
github.com/charmbracelet/huh v1.0.0
github.com/charmbracelet/lipgloss v1.1.0
golang.org/x/term v0.41.0
gopkg.in/yaml.v3 v3.0.1
)
require golang.org/x/sys v0.39.0 // indirect
require (
github.com/atotto/clipboard v0.1.4 // indirect
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/catppuccin/go v0.3.0 // indirect
github.com/charmbracelet/bubbles v0.21.1-0.20250623103423-23b8fd6302d7 // indirect
github.com/charmbracelet/bubbletea v1.3.6 // indirect
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect
github.com/charmbracelet/x/ansi v0.9.3 // indirect
github.com/charmbracelet/x/cellbuf v0.0.13 // indirect
github.com/charmbracelet/x/exp/strings v0.0.0-20240722160745-212f7b056ed0 // indirect
github.com/charmbracelet/x/term v0.2.1 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-localereader v0.0.1 // indirect
github.com/mattn/go-runewidth v0.0.16 // indirect
github.com/mitchellh/hashstructure/v2 v2.0.2 // indirect
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
github.com/muesli/cancelreader v0.2.2 // indirect
github.com/muesli/termenv v0.16.0 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
golang.org/x/sync v0.15.0 // indirect
golang.org/x/sys v0.42.0 // indirect
golang.org/x/text v0.23.0 // indirect
)
+77 -4
View File
@@ -1,7 +1,80 @@
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/term v0.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q=
golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg=
github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ=
github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE=
github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
github.com/aymanbagabas/go-udiff v0.3.1 h1:LV+qyBQ2pqe0u42ZsUEtPiCaUoqgA9gYRDs3vj1nolY=
github.com/aymanbagabas/go-udiff v0.3.1/go.mod h1:G0fsKmG+P6ylD0r6N/KgQD/nWzgfnl8ZBcNLgcbrw8E=
github.com/catppuccin/go v0.3.0 h1:d+0/YicIq+hSTo5oPuRi5kOpqkVA5tAsU6dNhvRu+aY=
github.com/catppuccin/go v0.3.0/go.mod h1:8IHJuMGaUUjQM82qBrGNBv7LFq6JI3NnQCF6MOlZjpc=
github.com/charmbracelet/bubbles v0.21.1-0.20250623103423-23b8fd6302d7 h1:JFgG/xnwFfbezlUnFMJy0nusZvytYysV4SCS2cYbvws=
github.com/charmbracelet/bubbles v0.21.1-0.20250623103423-23b8fd6302d7/go.mod h1:ISC1gtLcVilLOf23wvTfoQuYbW2q0JevFxPfUzZ9Ybw=
github.com/charmbracelet/bubbletea v1.3.6 h1:VkHIxPJQeDt0aFJIsVxw8BQdh/F/L2KKZGsK6et5taU=
github.com/charmbracelet/bubbletea v1.3.6/go.mod h1:oQD9VCRQFF8KplacJLo28/jofOI2ToOfGYeFgBBxHOc=
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc h1:4pZI35227imm7yK2bGPcfpFEmuY1gc2YSTShr4iJBfs=
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc/go.mod h1:X4/0JoqgTIPSFcRA/P6INZzIuyqdFY5rm8tb41s9okk=
github.com/charmbracelet/huh v1.0.0 h1:wOnedH8G4qzJbmhftTqrpppyqHakl/zbbNdXIWJyIxw=
github.com/charmbracelet/huh v1.0.0/go.mod h1:5YVc+SlZ1IhQALxRPpkGwwEKftN/+OlJlnJYlDRFqN4=
github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY=
github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30=
github.com/charmbracelet/x/ansi v0.9.3 h1:BXt5DHS/MKF+LjuK4huWrC6NCvHtexww7dMayh6GXd0=
github.com/charmbracelet/x/ansi v0.9.3/go.mod h1:3RQDQ6lDnROptfpWuUVIUG64bD2g2BgntdxH0Ya5TeE=
github.com/charmbracelet/x/cellbuf v0.0.13 h1:/KBBKHuVRbq1lYx5BzEHBAFBP8VcQzJejZ/IA3iR28k=
github.com/charmbracelet/x/cellbuf v0.0.13/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs=
github.com/charmbracelet/x/conpty v0.1.0 h1:4zc8KaIcbiL4mghEON8D72agYtSeIgq8FSThSPQIb+U=
github.com/charmbracelet/x/conpty v0.1.0/go.mod h1:rMFsDJoDwVmiYM10aD4bH2XiRgwI7NYJtQgl5yskjEQ=
github.com/charmbracelet/x/errors v0.0.0-20240508181413-e8d8b6e2de86 h1:JSt3B+U9iqk37QUU2Rvb6DSBYRLtWqFqfxf8l5hOZUA=
github.com/charmbracelet/x/errors v0.0.0-20240508181413-e8d8b6e2de86/go.mod h1:2P0UgXMEa6TsToMSuFqKFQR+fZTO9CNGUNokkPatT/0=
github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91 h1:payRxjMjKgx2PaCWLZ4p3ro9y97+TVLZNaRZgJwSVDQ=
github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U=
github.com/charmbracelet/x/exp/strings v0.0.0-20240722160745-212f7b056ed0 h1:qko3AQ4gK1MTS/de7F5hPGx6/k1u0w4TeYmBFwzYVP4=
github.com/charmbracelet/x/exp/strings v0.0.0-20240722160745-212f7b056ed0/go.mod h1:pBhA0ybfXv6hDjQUZ7hk1lVxBiUbupdw5R31yPUViVQ=
github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ=
github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg=
github.com/charmbracelet/x/termios v0.1.1 h1:o3Q2bT8eqzGnGPOYheoYS8eEleT5ZVNYNy8JawjaNZY=
github.com/charmbracelet/x/termios v0.1.1/go.mod h1:rB7fnv1TgOPOyyKRJ9o+AsTU/vK5WHJ2ivHeut/Pcwo=
github.com/charmbracelet/x/xpty v0.1.2 h1:Pqmu4TEJ8KeA9uSkISKMU3f+C1F6OGBn8ABuGlqCbtI=
github.com/charmbracelet/x/xpty v0.1.2/go.mod h1:XK2Z0id5rtLWcpeNiMYBccNNBrP2IJnzHI0Lq13Xzq4=
github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s=
github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4=
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM=
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mitchellh/hashstructure/v2 v2.0.2 h1:vGKWl0YJqUNxE8d+h8f6NJLcCJrgbhC4NcD46KavDd4=
github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE=
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI=
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo=
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc=
github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI=
golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo=
golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8=
golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/term v0.41.0 h1:QCgPso/Q3RTJx2Th4bDLqML4W6iJiaXFq2/ftQF13YU=
golang.org/x/term v0.41.0/go.mod h1:3pfBgksrReYfZ5lvYM0kSO0LIkAl4Yl2bXOkKP7Ec2A=
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+188 -72
View File
@@ -1,92 +1,208 @@
package main
import (
"bufio"
"errors"
"fmt"
"strings"
"syscall"
"os"
"strconv"
"github.com/charmbracelet/huh"
"golang.org/x/term"
)
func readString(reader *bufio.Reader, prompt string, defaultValue string) string {
// pangolinTheme is the custom theme using brand colors
var pangolinTheme = ThemePangolin()
// isAccessibleMode checks if we should use accessible mode (simple prompts)
// This is true for: non-TTY, TERM=dumb, or ACCESSIBLE env var set
func isAccessibleMode() bool {
// Check if stdin is not a terminal (piped input, CI, etc.)
if !term.IsTerminal(int(os.Stdin.Fd())) {
return true
}
// Check for dumb terminal
if os.Getenv("TERM") == "dumb" {
return true
}
// Check for explicit accessible mode request
if os.Getenv("ACCESSIBLE") != "" {
return true
}
return false
}
// handleAbort checks if the error is a user abort (Ctrl+C) and exits if so
func handleAbort(err error) {
if err != nil && errors.Is(err, huh.ErrUserAborted) {
fmt.Println("\nInstallation cancelled.")
os.Exit(0)
}
}
// runField runs a single field with the Pangolin theme, handling accessible mode
func runField(field huh.Field) error {
if isAccessibleMode() {
return field.RunAccessible(os.Stdout, os.Stdin)
}
form := huh.NewForm(huh.NewGroup(field)).WithTheme(pangolinTheme)
return form.Run()
}
func readString(prompt string, defaultValue string) string {
var value string
title := prompt
if defaultValue != "" {
fmt.Printf("%s (default: %s): ", prompt, defaultValue)
} else {
fmt.Print(prompt + ": ")
title = fmt.Sprintf("%s (default: %s)", prompt, defaultValue)
}
input, _ := reader.ReadString('\n')
input = strings.TrimSpace(input)
if input == "" {
return defaultValue
}
return input
}
func readStringNoDefault(reader *bufio.Reader, prompt string) string {
fmt.Print(prompt + ": ")
input, _ := reader.ReadString('\n')
return strings.TrimSpace(input)
}
input := huh.NewInput().
Title(title).
Value(&value)
func readPassword(prompt string, reader *bufio.Reader) string {
if term.IsTerminal(int(syscall.Stdin)) {
fmt.Print(prompt + ": ")
// Read password without echo if we're in a terminal
password, err := term.ReadPassword(int(syscall.Stdin))
fmt.Println() // Add a newline since ReadPassword doesn't add one
if err != nil {
return ""
}
input := strings.TrimSpace(string(password))
if input == "" {
return readPassword(prompt, reader)
}
return input
} else {
// Fallback to reading from stdin if not in a terminal
return readString(reader, prompt, "")
// If no default value, this field is required
if defaultValue == "" {
input = input.Validate(func(s string) error {
if s == "" {
return fmt.Errorf("this field is required")
}
return nil
})
}
}
func readBool(reader *bufio.Reader, prompt string, defaultValue bool) bool {
defaultStr := "no"
if defaultValue {
defaultStr = "yes"
}
for {
input := readString(reader, prompt+" (yes/no)", defaultStr)
lower := strings.ToLower(input)
if lower == "yes" {
return true
} else if lower == "no" {
return false
} else {
fmt.Println("Please enter 'yes' or 'no'.")
}
}
}
err := runField(input)
handleAbort(err)
func readBoolNoDefault(reader *bufio.Reader, prompt string) bool {
for {
input := readStringNoDefault(reader, prompt+" (yes/no)")
lower := strings.ToLower(input)
if lower == "yes" {
return true
} else if lower == "no" {
return false
} else {
fmt.Println("Please enter 'yes' or 'no'.")
}
if value == "" {
value = defaultValue
}
}
func readInt(reader *bufio.Reader, prompt string, defaultValue int) int {
input := readString(reader, prompt, fmt.Sprintf("%d", defaultValue))
if input == "" {
return defaultValue
// Print the answer so it remains visible in terminal history (skip in accessible mode as it already shows)
if !isAccessibleMode() {
fmt.Printf("%s: %s\n", prompt, value)
}
value := defaultValue
fmt.Sscanf(input, "%d", &value)
return value
}
func readPassword(prompt string) string {
var value string
for {
input := huh.NewInput().
Title(prompt).
Value(&value).
EchoMode(huh.EchoModePassword).
Validate(func(s string) error {
if s == "" {
return fmt.Errorf("password is required")
}
return nil
})
err := runField(input)
handleAbort(err)
if value != "" {
// Print confirmation without revealing the password
if !isAccessibleMode() {
fmt.Printf("%s: %s\n", prompt, "********")
}
return value
}
}
}
func readBool(prompt string, defaultValue bool) bool {
var value = defaultValue
confirm := huh.NewConfirm().
Title(prompt).
Value(&value).
Affirmative("Yes").
Negative("No")
err := runField(confirm)
handleAbort(err)
// Print the answer so it remains visible in terminal history
if !isAccessibleMode() {
answer := "No"
if value {
answer = "Yes"
}
fmt.Printf("%s: %s\n", prompt, answer)
}
return value
}
func readBoolNoDefault(prompt string) bool {
var value bool
confirm := huh.NewConfirm().
Title(prompt).
Value(&value).
Affirmative("Yes").
Negative("No")
err := runField(confirm)
handleAbort(err)
// Print the answer so it remains visible in terminal history
if !isAccessibleMode() {
answer := "No"
if value {
answer = "Yes"
}
fmt.Printf("%s: %s\n", prompt, answer)
}
return value
}
func readInt(prompt string, defaultValue int) int {
var value string
title := fmt.Sprintf("%s (default: %d)", prompt, defaultValue)
input := huh.NewInput().
Title(title).
Value(&value).
Validate(func(s string) error {
if s == "" {
return nil
}
_, err := strconv.Atoi(s)
if err != nil {
return fmt.Errorf("please enter a valid number")
}
return nil
})
err := runField(input)
handleAbort(err)
if value == "" {
// Print the answer so it remains visible in terminal history
if !isAccessibleMode() {
fmt.Printf("%s: %d\n", prompt, defaultValue)
}
return defaultValue
}
result, err := strconv.Atoi(value)
if err != nil {
if !isAccessibleMode() {
fmt.Printf("%s: %d\n", prompt, defaultValue)
}
return defaultValue
}
// Print the answer so it remains visible in terminal history
if !isAccessibleMode() {
fmt.Printf("%s: %d\n", prompt, result)
}
return result
}
+229 -102
View File
@@ -1,29 +1,35 @@
package main
import (
"bufio"
"crypto/rand"
"embed"
"encoding/base64"
"fmt"
"io"
"io/fs"
"math/rand"
"net"
"net/http"
"net/url"
"os"
"os/exec"
"path/filepath"
"runtime"
"strconv"
"strings"
"text/template"
"time"
)
// DO NOT EDIT THIS FUNCTION; IT MATCHED BY REGEX IN CICD
// Version variables injected at build time via -ldflags
var (
pangolinVersion string
gerbilVersion string
badgerVersion string
)
func loadVersions(config *Config) {
config.PangolinVersion = "replaceme"
config.GerbilVersion = "replaceme"
config.BadgerVersion = "replaceme"
config.PangolinVersion = pangolinVersion
config.GerbilVersion = gerbilVersion
config.BadgerVersion = badgerVersion
}
//go:embed config/*
@@ -81,14 +87,19 @@ func main() {
}
}
reader := bufio.NewReader(os.Stdin)
var config Config
var alreadyInstalled = false
// Determine installation directory
installDir := findOrSelectInstallDirectory()
if err := os.Chdir(installDir); err != nil {
fmt.Printf("Error changing to installation directory: %v\n", err)
os.Exit(1)
}
// check if there is already a config file
if _, err := os.Stat("config/config.yml"); err != nil {
config = collectUserInput(reader)
config = collectUserInput()
loadVersions(&config)
config.DoCrowdsecInstall = false
@@ -101,7 +112,10 @@ func main() {
os.Exit(1)
}
moveFile("config/docker-compose.yml", "docker-compose.yml")
if err := moveFile("config/docker-compose.yml", "docker-compose.yml"); err != nil {
fmt.Printf("Error moving docker-compose.yml: %v\n", err)
os.Exit(1)
}
fmt.Println("\nConfiguration files created successfully!")
@@ -116,13 +130,17 @@ func main() {
fmt.Println("\n=== Starting installation ===")
if readBool(reader, "Would you like to install and start the containers?", true) {
if readBool("Would you like to install and start the containers?", true) {
config.InstallationContainerType = podmanOrDocker(reader)
config.InstallationContainerType = podmanOrDocker()
if !isDockerInstalled() && runtime.GOOS == "linux" && config.InstallationContainerType == Docker {
if readBool(reader, "Docker is not installed. Would you like to install it?", true) {
installDocker()
if readBool("Docker is not installed. Would you like to install it?", true) {
if err := installDocker(); err != nil {
fmt.Printf("Error installing Docker: %v\n", err)
return
}
// try to start docker service but ignore errors
if err := startDockerService(); err != nil {
fmt.Println("Error starting Docker service:", err)
@@ -131,7 +149,7 @@ func main() {
}
// wait 10 seconds for docker to start checking if docker is running every 2 seconds
fmt.Println("Waiting for Docker to start...")
for i := 0; i < 5; i++ {
for range 5 {
if isDockerRunning() {
fmt.Println("Docker is running!")
break
@@ -166,7 +184,7 @@ func main() {
fmt.Println("\n=== MaxMind Database Update ===")
if _, err := os.Stat("config/GeoLite2-Country.mmdb"); err == nil {
fmt.Println("MaxMind GeoLite2 Country database found.")
if readBool(reader, "Would you like to update the MaxMind database to the latest version?", false) {
if readBool("Would you like to update the MaxMind database to the latest version?", false) {
if err := downloadMaxMindDatabase(); err != nil {
fmt.Printf("Error updating MaxMind database: %v\n", err)
fmt.Println("You can try updating it manually later if needed.")
@@ -174,7 +192,7 @@ func main() {
}
} else {
fmt.Println("MaxMind GeoLite2 Country database not found.")
if readBool(reader, "Would you like to download the MaxMind GeoLite2 database for geoblocking functionality?", false) {
if readBool("Would you like to download the MaxMind GeoLite2 database for geoblocking functionality?", false) {
if err := downloadMaxMindDatabase(); err != nil {
fmt.Printf("Error downloading MaxMind database: %v\n", err)
fmt.Println("You can try downloading it manually later if needed.")
@@ -191,11 +209,11 @@ func main() {
if !checkIsCrowdsecInstalledInCompose() {
fmt.Println("\n=== CrowdSec Install ===")
// check if crowdsec is installed
if readBool(reader, "Would you like to install CrowdSec?", false) {
if readBool("Would you like to install CrowdSec?", false) {
fmt.Println("This installer constitutes a minimal viable CrowdSec deployment. CrowdSec will add extra complexity to your Pangolin installation and may not work to the best of its abilities out of the box. Users are expected to implement configuration adjustments on their own to achieve the best security posture. Consult the CrowdSec documentation for detailed configuration instructions.")
// BUG: crowdsec installation will be skipped if the user chooses to install on the first installation.
if readBool(reader, "Are you willing to manage CrowdSec?", false) {
if readBool("Are you willing to manage CrowdSec?", false) {
if config.DashboardDomain == "" {
traefikConfig, err := ReadTraefikConfig("config/traefik/traefik_config.yml")
if err != nil {
@@ -224,12 +242,21 @@ func main() {
fmt.Printf("Let's Encrypt Email: %s\n", config.LetsEncryptEmail)
fmt.Printf("Badger Version: %s\n", config.BadgerVersion)
if !readBool(reader, "Are these values correct?", true) {
config = collectUserInput(reader)
if !readBool("Are these values correct?", true) {
config = collectUserInput()
}
}
config.InstallationContainerType = podmanOrDocker(reader)
// Try to detect container type from existing installation
detectedType := detectContainerType()
if detectedType == Undefined {
// If detection fails, prompt the user
fmt.Println("Unable to detect container type from existing installation.")
config.InstallationContainerType = podmanOrDocker()
} else {
config.InstallationContainerType = detectedType
fmt.Printf("Detected container type: %s\n", config.InstallationContainerType)
}
config.DoCrowdsecInstall = true
err := installCrowdsec(config)
@@ -267,8 +294,119 @@ func main() {
fmt.Printf("\nTo complete the initial setup, please visit:\nhttps://%s/auth/initial-setup\n", config.DashboardDomain)
}
func podmanOrDocker(reader *bufio.Reader) SupportedContainer {
inputContainer := readString(reader, "Would you like to run Pangolin as Docker or Podman containers?", "docker")
func hasExistingInstall(dir string) bool {
configPath := filepath.Join(dir, "config", "config.yml")
_, err := os.Stat(configPath)
return err == nil
}
func findOrSelectInstallDirectory() string {
const defaultInstallDir = "/opt/pangolin"
// Get current working directory
cwd, err := os.Getwd()
if err != nil {
fmt.Printf("Error getting current directory: %v\n", err)
os.Exit(1)
}
// 1. Check current directory for existing install
if hasExistingInstall(cwd) {
fmt.Printf("Found existing Pangolin installation in current directory: %s\n", cwd)
return cwd
}
// 2. Check default location (/opt/pangolin) for existing install
if cwd != defaultInstallDir && hasExistingInstall(defaultInstallDir) {
fmt.Printf("\nFound existing Pangolin installation at: %s\n", defaultInstallDir)
if readBool(fmt.Sprintf("Would you like to use the existing installation at %s?", defaultInstallDir), true) {
return defaultInstallDir
}
}
// 3. No existing install found, prompt for installation directory
fmt.Println("\n=== Installation Directory ===")
fmt.Println("No existing Pangolin installation detected.")
installDir := readString("Enter the installation directory", defaultInstallDir)
// Expand ~ to home directory if present
if strings.HasPrefix(installDir, "~") {
home, err := os.UserHomeDir()
if err != nil {
fmt.Printf("Error getting home directory: %v\n", err)
os.Exit(1)
}
installDir = filepath.Join(home, installDir[1:])
}
// Convert to absolute path
absPath, err := filepath.Abs(installDir)
if err != nil {
fmt.Printf("Error resolving path: %v\n", err)
os.Exit(1)
}
installDir = absPath
// Check if directory exists
if _, err := os.Stat(installDir); os.IsNotExist(err) {
// Directory doesn't exist, create it
if readBool(fmt.Sprintf("Directory %s does not exist. Create it?", installDir), true) {
if err := os.MkdirAll(installDir, 0755); err != nil {
fmt.Printf("Error creating directory: %v\n", err)
os.Exit(1)
}
fmt.Printf("Created directory: %s\n", installDir)
// Offer to change ownership if running via sudo
changeDirectoryOwnership(installDir)
} else {
fmt.Println("Installation cancelled.")
os.Exit(0)
}
}
fmt.Printf("Installation directory: %s\n", installDir)
return installDir
}
func changeDirectoryOwnership(dir string) {
// Check if we're running via sudo by looking for SUDO_USER
sudoUser := os.Getenv("SUDO_USER")
if sudoUser == "" || os.Geteuid() != 0 {
return
}
sudoUID := os.Getenv("SUDO_UID")
sudoGID := os.Getenv("SUDO_GID")
if sudoUID == "" || sudoGID == "" {
return
}
fmt.Printf("\nRunning as root via sudo (original user: %s)\n", sudoUser)
if readBool(fmt.Sprintf("Would you like to change ownership of %s to user '%s'? This makes it easier to manage config files without sudo.", dir, sudoUser), true) {
uid, err := strconv.Atoi(sudoUID)
if err != nil {
fmt.Printf("Warning: Could not parse SUDO_UID: %v\n", err)
return
}
gid, err := strconv.Atoi(sudoGID)
if err != nil {
fmt.Printf("Warning: Could not parse SUDO_GID: %v\n", err)
return
}
if err := os.Chown(dir, uid, gid); err != nil {
fmt.Printf("Warning: Could not change ownership: %v\n", err)
} else {
fmt.Printf("Changed ownership of %s to %s\n", dir, sudoUser)
}
}
}
func podmanOrDocker() SupportedContainer {
inputContainer := readString("Would you like to run Pangolin as Docker or Podman containers?", "docker")
chosenContainer := Docker
if strings.EqualFold(inputContainer, "docker") {
@@ -280,16 +418,17 @@ func podmanOrDocker(reader *bufio.Reader) SupportedContainer {
os.Exit(1)
}
if chosenContainer == Podman {
switch chosenContainer {
case Podman:
if !isPodmanInstalled() {
fmt.Println("Podman or podman-compose is not installed. Please install both manually. Automated installation will be available in a later release.")
os.Exit(1)
}
if err := exec.Command("bash", "-c", "cat /etc/sysctl.conf | grep 'net.ipv4.ip_unprivileged_port_start='").Run(); err != nil {
if err := exec.Command("bash", "-c", "cat /etc/sysctl.d/99-podman.conf 2>/dev/null | grep 'net.ipv4.ip_unprivileged_port_start=' || cat /etc/sysctl.conf 2>/dev/null | grep 'net.ipv4.ip_unprivileged_port_start='").Run(); err != nil {
fmt.Println("Would you like to configure ports >= 80 as unprivileged ports? This enables podman containers to listen on low-range ports.")
fmt.Println("Pangolin will experience startup issues if this is not configured, because it needs to listen on port 80/443 by default.")
approved := readBool(reader, "The installer is about to execute \"echo 'net.ipv4.ip_unprivileged_port_start=80' >> /etc/sysctl.conf && sysctl -p\". Approve?", true)
approved := readBool("The installer is about to execute \"echo 'net.ipv4.ip_unprivileged_port_start=80' > /etc/sysctl.d/99-podman.conf && sysctl --system\". Approve?", true)
if approved {
if os.Geteuid() != 0 {
fmt.Println("You need to run the installer as root for such a configuration.")
@@ -300,8 +439,8 @@ func podmanOrDocker(reader *bufio.Reader) SupportedContainer {
// container low-range ports as unprivileged ports.
// Linux only.
if err := run("bash", "-c", "echo 'net.ipv4.ip_unprivileged_port_start=80' >> /etc/sysctl.conf && sysctl -p"); err != nil {
fmt.Printf("Error configuring unprivileged ports: %v\n", err)
if err := run("bash", "-c", "echo 'net.ipv4.ip_unprivileged_port_start=80' > /etc/sysctl.d/99-podman.conf && sysctl --system"); err != nil {
fmt.Printf("Error configuring unprivileged ports: %v\n", err)
os.Exit(1)
}
} else {
@@ -311,7 +450,7 @@ func podmanOrDocker(reader *bufio.Reader) SupportedContainer {
fmt.Println("Unprivileged ports have been configured.")
}
} else if chosenContainer == Docker {
case Docker:
// check if docker is not installed and the user is root
if !isDockerInstalled() {
if os.Geteuid() != 0 {
@@ -326,7 +465,7 @@ func podmanOrDocker(reader *bufio.Reader) SupportedContainer {
fmt.Println("The installer will not be able to run docker commands without running it as root.")
os.Exit(1)
}
} else {
default:
// This shouldn't happen unless there's a third container runtime.
os.Exit(1)
}
@@ -334,35 +473,35 @@ func podmanOrDocker(reader *bufio.Reader) SupportedContainer {
return chosenContainer
}
func collectUserInput(reader *bufio.Reader) Config {
func collectUserInput() Config {
config := Config{}
// Basic configuration
fmt.Println("\n=== Basic Configuration ===")
config.IsEnterprise = readBoolNoDefault(reader, "Do you want to install the Enterprise version of Pangolin? The EE is free for persoal use or for businesses making less than 100k USD annually.")
config.IsEnterprise = readBoolNoDefault("Do you want to install the Enterprise version of Pangolin? The EE is free for personal use or for businesses making less than 100k USD annually.")
config.BaseDomain = readString(reader, "Enter your base domain (no subdomain e.g. example.com)", "")
config.BaseDomain = readString("Enter your base domain (no subdomain e.g. example.com)", "")
// Set default dashboard domain after base domain is collected
defaultDashboardDomain := ""
if config.BaseDomain != "" {
defaultDashboardDomain = "pangolin." + config.BaseDomain
}
config.DashboardDomain = readString(reader, "Enter the domain for the Pangolin dashboard", defaultDashboardDomain)
config.LetsEncryptEmail = readString(reader, "Enter email for Let's Encrypt certificates", "")
config.InstallGerbil = readBool(reader, "Do you want to use Gerbil to allow tunneled connections", true)
config.DashboardDomain = readString("Enter the domain for the Pangolin dashboard", defaultDashboardDomain)
config.LetsEncryptEmail = readString("Enter email for Let's Encrypt certificates", "")
config.InstallGerbil = readBool("Do you want to use Gerbil to allow tunneled connections", true)
// Email configuration
fmt.Println("\n=== Email Configuration ===")
config.EnableEmail = readBool(reader, "Enable email functionality (SMTP)", false)
config.EnableEmail = readBool("Enable email functionality (SMTP)", false)
if config.EnableEmail {
config.EmailSMTPHost = readString(reader, "Enter SMTP host", "")
config.EmailSMTPPort = readInt(reader, "Enter SMTP port (default 587)", 587)
config.EmailSMTPUser = readString(reader, "Enter SMTP username", "")
config.EmailSMTPPass = readString(reader, "Enter SMTP password", "") // Should this be readPassword?
config.EmailNoReply = readString(reader, "Enter no-reply email address (often the same as SMTP username)", "")
config.EmailSMTPHost = readString("Enter SMTP host", "")
config.EmailSMTPPort = readInt("Enter SMTP port (default 587)", 587)
config.EmailSMTPUser = readString("Enter SMTP username", "")
config.EmailSMTPPass = readPassword("Enter SMTP password")
config.EmailNoReply = readString("Enter no-reply email address (often the same as SMTP username)", "")
}
// Validate required fields
@@ -383,8 +522,8 @@ func collectUserInput(reader *bufio.Reader) Config {
fmt.Println("\n=== Advanced Configuration ===")
config.EnableIPv6 = readBool(reader, "Is your server IPv6 capable?", true)
config.EnableGeoblocking = readBool(reader, "Do you want to download the MaxMind GeoLite2 database for geoblocking functionality?", true)
config.EnableIPv6 = readBool("Is your server IPv6 capable?", true)
config.EnableGeoblocking = readBool("Do you want to download the MaxMind GeoLite2 database for geoblocking functionality?", true)
if config.DashboardDomain == "" {
fmt.Println("Error: Dashboard Domain name is required")
@@ -395,15 +534,23 @@ func collectUserInput(reader *bufio.Reader) Config {
}
func createConfigFiles(config Config) error {
os.MkdirAll("config", 0755)
os.MkdirAll("config/letsencrypt", 0755)
os.MkdirAll("config/db", 0755)
os.MkdirAll("config/logs", 0755)
if err := os.MkdirAll("config", 0755); err != nil {
return fmt.Errorf("failed to create config directory: %v", err)
}
if err := os.MkdirAll("config/letsencrypt", 0755); err != nil {
return fmt.Errorf("failed to create letsencrypt directory: %v", err)
}
if err := os.MkdirAll("config/db", 0755); err != nil {
return fmt.Errorf("failed to create db directory: %v", err)
}
if err := os.MkdirAll("config/logs", 0755); err != nil {
return fmt.Errorf("failed to create logs directory: %v", err)
}
// Walk through all embedded files
err := fs.WalkDir(configFiles, "config", func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
err := fs.WalkDir(configFiles, "config", func(path string, d fs.DirEntry, walkErr error) (err error) {
if walkErr != nil {
return walkErr
}
// Skip the root fs directory itself
@@ -454,7 +601,11 @@ func createConfigFiles(config Config) error {
if err != nil {
return fmt.Errorf("failed to create %s: %v", path, err)
}
defer outFile.Close()
defer func() {
if cerr := outFile.Close(); cerr != nil && err == nil {
err = cerr
}
}()
// Execute template
if err := tmpl.Execute(outFile, config); err != nil {
@@ -470,18 +621,26 @@ func createConfigFiles(config Config) error {
return nil
}
func copyFile(src, dst string) error {
func copyFile(src, dst string) (err error) {
source, err := os.Open(src)
if err != nil {
return err
}
defer source.Close()
defer func() {
if cerr := source.Close(); cerr != nil && err == nil {
err = cerr
}
}()
destination, err := os.Create(dst)
if err != nil {
return err
}
defer destination.Close()
defer func() {
if cerr := destination.Close(); cerr != nil && err == nil {
err = cerr
}
}()
_, err = io.Copy(destination, source)
return err
@@ -552,22 +711,24 @@ func showSetupTokenInstructions(containerType SupportedContainer, dashboardDomai
fmt.Println("To get your setup token, you need to:")
fmt.Println("")
fmt.Println("1. Start the containers")
if containerType == Docker {
switch containerType {
case Docker:
fmt.Println(" docker compose up -d")
} else if containerType == Podman {
case Podman:
fmt.Println(" podman-compose up -d")
} else {
}
fmt.Println("")
fmt.Println("2. Wait for the Pangolin container to start and generate the token")
fmt.Println("")
fmt.Println("3. Check the container logs for the setup token")
if containerType == Docker {
switch containerType {
case Docker:
fmt.Println(" docker logs pangolin | grep -A 2 -B 2 'SETUP TOKEN'")
} else if containerType == Podman {
case Podman:
fmt.Println(" podman logs pangolin | grep -A 2 -B 2 'SETUP TOKEN'")
} else {
}
fmt.Println("")
fmt.Println("4. Look for output like")
fmt.Println(" === SETUP TOKEN GENERATED ===")
@@ -583,43 +744,12 @@ func showSetupTokenInstructions(containerType SupportedContainer, dashboardDomai
}
func generateRandomSecretKey() string {
const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
const length = 32
var seededRand *rand.Rand = rand.New(
rand.NewSource(time.Now().UnixNano()))
b := make([]byte, length)
for i := range b {
b[i] = charset[seededRand.Intn(len(charset))]
}
return string(b)
}
func getPublicIP() string {
client := &http.Client{
Timeout: 10 * time.Second,
}
resp, err := client.Get("https://ifconfig.io/ip")
secret := make([]byte, 32)
_, err := rand.Read(secret)
if err != nil {
return ""
panic(fmt.Sprintf("Failed to generate random secret key: %v", err))
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return ""
}
ip := strings.TrimSpace(string(body))
// Validate that it's a valid IP address
if net.ParseIP(ip) != nil {
return ip
}
return ""
return base64.StdEncoding.EncodeToString(secret)
}
// Run external commands with stdio/stderr attached.
@@ -634,10 +764,7 @@ func checkPortsAvailable(port int) error {
addr := fmt.Sprintf(":%d", port)
ln, err := net.Listen("tcp", addr)
if err != nil {
return fmt.Errorf(
"ERROR: port %d is occupied or cannot be bound: %w\n\n",
port, err,
)
return fmt.Errorf("ERROR: port %d is occupied or cannot be bound: %w", port, err)
}
if closeErr := ln.Close(); closeErr != nil {
fmt.Fprintf(os.Stderr,
+51
View File
@@ -0,0 +1,51 @@
package main
import (
"github.com/charmbracelet/huh"
"github.com/charmbracelet/lipgloss"
)
// Pangolin brand colors (converted from oklch to hex)
var (
// Primary orange/amber - oklch(0.6717 0.1946 41.93)
primaryColor = lipgloss.AdaptiveColor{Light: "#D97706", Dark: "#F59E0B"}
// Muted foreground
mutedColor = lipgloss.AdaptiveColor{Light: "#737373", Dark: "#A3A3A3"}
// Success green
successColor = lipgloss.AdaptiveColor{Light: "#16A34A", Dark: "#22C55E"}
// Error red - oklch(0.577 0.245 27.325)
errorColor = lipgloss.AdaptiveColor{Light: "#DC2626", Dark: "#EF4444"}
// Normal text
normalFg = lipgloss.AdaptiveColor{Light: "#171717", Dark: "#FAFAFA"}
)
// ThemePangolin returns a huh theme using Pangolin brand colors
func ThemePangolin() *huh.Theme {
t := huh.ThemeBase()
// Focused state styles
t.Focused.Base = t.Focused.Base.BorderForeground(primaryColor)
t.Focused.Title = t.Focused.Title.Foreground(primaryColor).Bold(true)
t.Focused.Description = t.Focused.Description.Foreground(mutedColor)
t.Focused.ErrorIndicator = t.Focused.ErrorIndicator.Foreground(errorColor)
t.Focused.ErrorMessage = t.Focused.ErrorMessage.Foreground(errorColor)
t.Focused.SelectSelector = t.Focused.SelectSelector.Foreground(primaryColor)
t.Focused.NextIndicator = t.Focused.NextIndicator.Foreground(primaryColor)
t.Focused.PrevIndicator = t.Focused.PrevIndicator.Foreground(primaryColor)
t.Focused.Option = t.Focused.Option.Foreground(normalFg)
t.Focused.SelectedOption = t.Focused.SelectedOption.Foreground(primaryColor)
t.Focused.SelectedPrefix = lipgloss.NewStyle().Foreground(successColor).SetString("✓ ")
t.Focused.UnselectedPrefix = lipgloss.NewStyle().Foreground(mutedColor).SetString(" ")
t.Focused.FocusedButton = t.Focused.FocusedButton.Foreground(lipgloss.Color("#FFFFFF")).Background(primaryColor)
t.Focused.BlurredButton = t.Focused.BlurredButton.Foreground(normalFg).Background(lipgloss.AdaptiveColor{Light: "#E5E5E5", Dark: "#404040"})
t.Focused.TextInput.Cursor = t.Focused.TextInput.Cursor.Foreground(primaryColor)
t.Focused.TextInput.Prompt = t.Focused.TextInput.Prompt.Foreground(primaryColor)
// Blurred state inherits from focused but with hidden border
t.Blurred = t.Focused
t.Blurred.Base = t.Focused.Base.BorderStyle(lipgloss.HiddenBorder())
t.Blurred.Title = t.Blurred.Title.Foreground(mutedColor).Bold(false)
t.Blurred.TextInput.Prompt = t.Blurred.TextInput.Prompt.Foreground(mutedColor)
return t
}
+137
View File
@@ -0,0 +1,137 @@
import os
import sys
# --- Configuration ---
# The header text to be added to the files.
HEADER_TEXT = """/*
* This file is part of a proprietary work.
*
* Copyright (c) 2025-2026 Fossorial, Inc.
* All rights reserved.
*
* This file is licensed under the Fossorial Commercial License.
* You may not use this file except in compliance with the License.
* Unauthorized use, copying, modification, or distribution is strictly prohibited.
*
* This file is not licensed under the AGPLv3.
*/
"""
HEADER_NORMALIZED = HEADER_TEXT.strip()
def extract_leading_block_comment(content):
"""
If the file content begins with a /* ... */ block comment, return the
full text of that comment (including the delimiters) and the index at
which the rest of the file starts (after any trailing newlines).
Returns (None, 0) when no such comment is found.
"""
stripped = content.lstrip()
if not stripped.startswith('/*'):
return None, 0
# Account for any leading whitespace before the comment
comment_start = content.index('/*')
end_marker = content.find('*/', comment_start + 2)
if end_marker == -1:
return None, 0
comment_end = end_marker + 2 # position just after '*/'
comment_text = content[comment_start:comment_end].strip()
# Advance past any whitespace / newlines that follow the closing */
rest_start = comment_end
while rest_start < len(content) and content[rest_start] in '\n\r':
rest_start += 1
return comment_text, rest_start
def should_add_header(file_path):
"""
Checks if a file should receive the commercial license header.
Returns True if 'server/private' is in the path.
"""
if 'server/private' in file_path.lower():
return True
return False
def process_directory(root_dir):
"""
Recursively scans a directory and adds/replaces/removes headers in
qualifying .ts or .tsx files, skipping any 'node_modules' directories.
"""
print(f"Scanning directory: {root_dir}")
files_processed = 0
files_modified = 0
for root, dirs, files in os.walk(root_dir):
# Exclude 'node_modules' directories from the scan.
if 'node_modules' in dirs:
dirs.remove('node_modules')
for file in files:
if not (file.endswith('.ts') or file.endswith('.tsx')):
continue
file_path = os.path.join(root, file)
files_processed += 1
try:
with open(file_path, 'r', encoding='utf-8') as f:
original_content = f.read()
existing_comment, body_start = extract_leading_block_comment(
original_content
)
has_any_header = existing_comment is not None
has_correct_header = existing_comment == HEADER_NORMALIZED
body = original_content[body_start:] if has_any_header else original_content
if should_add_header(file_path):
if has_correct_header:
print(f"Header up-to-date: {file_path}")
else:
# Either no header exists or the header is outdated — write
# the correct one.
action = "Replaced header in" if has_any_header else "Added header to"
new_content = HEADER_NORMALIZED + '\n\n' + body
with open(file_path, 'w', encoding='utf-8') as f:
f.write(new_content)
print(f"{action}: {file_path}")
files_modified += 1
else:
if has_any_header:
# Remove the header — it shouldn't be here.
with open(file_path, 'w', encoding='utf-8') as f:
f.write(body)
print(f"Removed header from: {file_path}")
files_modified += 1
else:
print(f"No header needed: {file_path}")
except Exception as e:
print(f"Error processing file {file_path}: {e}")
print("\n--- Scan Complete ---")
print(f"Total .ts or .tsx files found: {files_processed}")
print(f"Files modified (added/replaced/removed): {files_modified}")
if __name__ == "__main__":
# Get the target directory from the command line arguments.
# If no directory is provided, it uses the current directory ('.').
if len(sys.argv) > 1:
target_directory = sys.argv[1]
else:
target_directory = '.' # Default to current directory
if not os.path.isdir(target_directory):
print(f"Error: Directory '{target_directory}' not found.")
sys.exit(1)
process_directory(os.path.abspath(target_directory))
+566 -40
View File
File diff suppressed because it is too large Load Diff
+566 -40
View File
File diff suppressed because it is too large Load Diff
+566 -40
View File
File diff suppressed because it is too large Load Diff
+564 -39
View File
File diff suppressed because it is too large Load Diff
+563 -37
View File
@@ -18,6 +18,8 @@
"componentsMember": "Eres un miembro de {count, plural, =0 {ninguna organización} one {una organización} other {# organizaciones}}.",
"componentsInvalidKey": "Se han detectado claves de licencia inválidas o caducadas. Siga los términos de licencia para seguir usando todas las características.",
"dismiss": "Descartar",
"subscriptionViolationMessage": "Estás más allá de tus límites para tu plan actual. Corrija el problema eliminando sitios, usuarios u otros recursos para permanecer dentro de tu plan.",
"subscriptionViolationViewBilling": "Ver facturación",
"componentsLicenseViolation": "Violación de la Licencia: Este servidor está usando sitios {usedSites} que exceden su límite de licencias de sitios {maxSites} . Siga los términos de licencia para seguir usando todas las características.",
"componentsSupporterMessage": "¡Gracias por apoyar a Pangolin como {tier}!",
"inviteErrorNotValid": "Lo sentimos, pero parece que la invitación a la que intentas acceder no ha sido aceptada o ya no es válida.",
@@ -56,6 +58,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": "Aprobar o denegar el acceso al dispositivo",
"approvalsBannerDescription": "Revisar y aprobar o denegar las solicitudes de acceso al dispositivo de los usuarios. Cuando se requieren aprobaciones de dispositivos, los usuarios deben obtener la aprobación del administrador antes de que sus dispositivos puedan conectarse a los recursos de su organización.",
"approvalsBannerButtonText": "Saber más",
"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",
@@ -143,6 +148,11 @@
"createLink": "Crear enlace",
"resourcesNotFound": "No se encontraron recursos",
"resourceSearch": "Buscar recursos",
"machineSearch": "Buscar máquinas",
"machinesSearch": "Buscar clientes...",
"machineNotFound": "No hay máquinas",
"userDeviceSearch": "Buscar dispositivos de usuario",
"userDevicesSearch": "Buscar dispositivos de usuario...",
"openMenu": "Abrir menú",
"resource": "Recurso",
"title": "Título",
@@ -153,7 +163,7 @@
"proxyResourceTitle": "Administrar recursos públicos",
"proxyResourceDescription": "Crear y administrar recursos que sean accesibles públicamente a través de un navegador web",
"proxyResourcesBannerTitle": "Acceso público basado en web",
"proxyResourcesBannerDescription": "Los recursos públicos son proxies HTTPS o TCP/UDP accesibles a cualquiera en Internet a través de un navegador web. A diferencia de los recursos privados, no requieren software del lado del cliente e incluye políticas de acceso basadas en identidad y contexto.",
"proxyResourcesBannerDescription": "Public resources are HTTPS proxies accessible to anyone on the internet through a web browser. Unlike private resources, they do not require client-side software and can include identity and context-aware access policies.",
"clientResourceTitle": "Administrar recursos privados",
"clientResourceDescription": "Crear y administrar recursos que sólo son accesibles a través de un cliente conectado",
"privateResourcesBannerTitle": "Acceso privado de confianza cero",
@@ -170,6 +180,7 @@
"resourceHTTPDescription": "Proxy proporciona solicitudes sobre HTTPS usando un nombre de dominio completamente calificado.",
"resourceRaw": "Recurso TCP/UDP sin procesar",
"resourceRawDescription": "Proxy proporciona solicitudes sobre TCP/UDP usando un número de puerto.",
"resourceRawDescriptionCloud": "Las peticiones de proxy sobre TCP/UDP crudas usando un número de puerto. Requiere que los sitios se conecten a un nodo remoto.",
"resourceCreate": "Crear Recurso",
"resourceCreateDescription": "Siga los siguientes pasos para crear un nuevo recurso",
"resourceSeeAll": "Ver todos los recursos",
@@ -196,6 +207,7 @@
"protocolSelect": "Seleccionar un protocolo",
"resourcePortNumber": "Número de puerto",
"resourcePortNumberDescription": "El número de puerto externo a las solicitudes de proxy.",
"back": "Atrás",
"cancel": "Cancelar",
"resourceConfig": "Fragmentos de configuración",
"resourceConfigDescription": "Copia y pega estos fragmentos de configuración para configurar el recurso TCP/UDP",
@@ -241,6 +253,17 @@
"orgErrorDeleteMessage": "Se ha producido un error al eliminar la organización.",
"orgDeleted": "Organización eliminada",
"orgDeletedMessage": "La organización y sus datos han sido eliminados.",
"deleteAccount": "Eliminar cuenta",
"deleteAccountDescription": "Elimina permanentemente tu cuenta, todas las organizaciones que posees y todos los datos dentro de esas organizaciones. Esto no se puede deshacer.",
"deleteAccountButton": "Eliminar cuenta",
"deleteAccountConfirmTitle": "Eliminar cuenta",
"deleteAccountConfirmMessage": "Esto borrará permanentemente tu cuenta, todas las organizaciones que posees y todos los datos dentro de esas organizaciones. Esto no se puede deshacer.",
"deleteAccountConfirmString": "eliminar cuenta",
"deleteAccountSuccess": "Cuenta eliminada",
"deleteAccountSuccessMessage": "Tu cuenta ha sido eliminada.",
"deleteAccountError": "Error al eliminar la cuenta",
"deleteAccountPreviewAccount": "Tu cuenta",
"deleteAccountPreviewOrgs": "Organizaciones que tienes (y todos sus datos)",
"orgMissing": "Falta el ID de la organización",
"orgMissingMessage": "No se puede regenerar la invitación sin el ID de la organización.",
"accessUsersManage": "Administrar usuarios",
@@ -257,6 +280,8 @@
"accessRolesSearch": "Buscar roles...",
"accessRolesAdd": "Añadir rol",
"accessRoleDelete": "Eliminar rol",
"accessApprovalsManage": "Administrar aprobaciones",
"accessApprovalsDescription": "Ver y administrar aprobaciones pendientes para el acceso a esta organización",
"description": "Descripción",
"inviteTitle": "Invitaciones abiertas",
"inviteDescription": "Administrar invitaciones para que otros usuarios se unan a la organización",
@@ -303,11 +328,59 @@
"apiKeysDelete": "Borrar Clave API",
"apiKeysManage": "Administrar claves API",
"apiKeysDescription": "Las claves API se utilizan para autenticar con la API de integración",
"provisioningKeysTitle": "Clave de aprovisionamiento",
"provisioningKeysManage": "Administrar Claves de Aprovisionamiento",
"provisioningKeysDescription": "Las claves de aprovisionamiento se utilizan para autenticar la provisión automatizada del sitio para su organización.",
"provisioningManage": "Aprovisionamiento",
"provisioningDescription": "Administrar las claves de aprovisionamiento y revisar los sitios pendientes de aprobación.",
"pendingSites": "Sitios pendientes",
"siteApproveSuccess": "Sitio aprobado con éxito",
"siteApproveError": "Error al aprobar el sitio",
"provisioningKeys": "Claves de aprovisionamiento",
"searchProvisioningKeys": "Buscar claves de suministro...",
"provisioningKeysAdd": "Generar clave de aprovisionamiento",
"provisioningKeysErrorDelete": "Error al eliminar la clave de aprovisionamiento",
"provisioningKeysErrorDeleteMessage": "Error al eliminar la clave de aprovisionamiento",
"provisioningKeysQuestionRemove": "¿Está seguro que desea eliminar esta clave de aprovisionamiento de la organización?",
"provisioningKeysMessageRemove": "Una vez eliminada, la clave ya no se puede utilizar para la disposición del sitio.",
"provisioningKeysDeleteConfirm": "Confirmar Eliminar Clave de Aprovisionamiento",
"provisioningKeysDelete": "Eliminar clave de aprovisionamiento",
"provisioningKeysCreate": "Generar clave de aprovisionamiento",
"provisioningKeysCreateDescription": "Generar una nueva clave de aprovisionamiento para la organización",
"provisioningKeysSeeAll": "Ver todas las claves de aprovisionamiento",
"provisioningKeysSave": "Guardar la clave de aprovisionamiento",
"provisioningKeysSaveDescription": "Sólo podrás verlo una vez. Copítalo a un lugar seguro.",
"provisioningKeysErrorCreate": "Error al crear la clave de provisioning",
"provisioningKeysList": "Nueva clave de aprovisionamiento",
"provisioningKeysMaxBatchSize": "Tamaño máximo de lote",
"provisioningKeysUnlimitedBatchSize": "Tamaño ilimitado del lote (sin límite)",
"provisioningKeysMaxBatchUnlimited": "Ilimitado",
"provisioningKeysMaxBatchSizeInvalid": "Introduzca un tamaño máximo de lote válido (11,000,000).",
"provisioningKeysValidUntil": "Válido hasta",
"provisioningKeysValidUntilHint": "Dejar vacío para no expirar.",
"provisioningKeysValidUntilInvalid": "Introduzca una fecha y hora válidas.",
"provisioningKeysNumUsed": "Tiempos usados",
"provisioningKeysLastUsed": "Último uso",
"provisioningKeysNoExpiry": "No expiración",
"provisioningKeysNeverUsed": "Nunca",
"provisioningKeysEdit": "Editar clave de aprovisionamiento",
"provisioningKeysEditDescription": "Actualizar el tamaño máximo de lote y el tiempo de caducidad para esta clave.",
"provisioningKeysApproveNewSites": "Aprobar nuevos sitios",
"provisioningKeysApproveNewSitesDescription": "Aprobar automáticamente los sitios que se registran con esta clave.",
"provisioningKeysUpdateError": "Error al actualizar la clave de aprovisionamiento",
"provisioningKeysUpdated": "Clave de aprovisionamiento actualizada",
"provisioningKeysUpdatedDescription": "Sus cambios han sido guardados.",
"provisioningKeysBannerTitle": "Claves de aprovisionamiento del sitio",
"provisioningKeysBannerDescription": "Genere una clave de aprovisionamiento y utilícela con el conector Newt para crear automáticamente sitios en el primer inicio: no es necesario configurar credenciales separadas para cada sitio.",
"provisioningKeysBannerButtonText": "Saber más",
"pendingSitesBannerTitle": "Sitios pendientes",
"pendingSitesBannerDescription": "Los sitios que se conectan utilizando una clave de aprovisionamiento aparecerán aquí para su revisión.",
"pendingSitesBannerButtonText": "Saber más",
"apiKeysSettings": "Ajustes {apiKeyName}",
"userTitle": "Administrar todos los usuarios",
"userDescription": "Ver y administrar todos los usuarios en el sistema",
"userAbount": "Acerca de Gestión de Usuarios",
"userAbountDescription": "Esta tabla muestra todos los objetos de usuario root en el sistema. Cada usuario puede pertenecer a varias organizaciones. Eliminar un usuario de una organización no elimina su objeto de usuario root - permanecerán en el sistema. Para eliminar completamente un usuario del sistema, debe eliminar su objeto de usuario root usando la acción de borrar en esta tabla.",
"userAbountDescription": "This table displays all base user objects in the system. Each user may belong to multiple organizations. Removing a user from an organization does not delete their base user object. They will remain in the system. To completely remove a user from the system, you must delete their base user object using the delete action in this table.",
"userServer": "Usuarios del servidor",
"userSearch": "Buscar usuarios del servidor...",
"userErrorDelete": "Error al eliminar el usuario",
@@ -332,6 +405,10 @@
"licenseErrorKeyActivate": "Error al activar la clave de licencia",
"licenseErrorKeyActivateDescription": "Se ha producido un error al activar la clave de licencia.",
"licenseAbout": "Acerca de la licencia",
"licenseBannerTitle": "Habilitar su Licencia Enterprise",
"licenseBannerDescription": "Desbloquea funciones empresariales para tu instancia autohospedada de Pangolin. Compra una clave de licencia para activar capacidades premium, luego agréguela a continuación.",
"licenseBannerGetLicense": "Obtener una Licencia",
"licenseBannerViewDocs": "Ver Documentación",
"communityEdition": "Edición comunitaria",
"licenseAboutDescription": "Esto es para usuarios empresariales y empresariales que utilizan Pangolin en un entorno comercial. Si estás usando Pangolin para uso personal, puedes ignorar esta sección.",
"licenseKeyActivated": "Clave de licencia activada",
@@ -446,10 +523,24 @@
"userSettings": "Información del usuario",
"userSettingsDescription": "Introduzca los detalles del nuevo usuario",
"inviteEmailSent": "Enviar correo de invitación al usuario",
"inviteValid": "Válido para",
"inviteValid": "Invite Valid For (days)",
"selectDuration": "Seleccionar duración",
"selectResource": "Seleccionar Recurso",
"filterByResource": "Filtrar por Recurso",
"selectApprovalState": "Seleccionar Estado de Aprobación",
"filterByApprovalState": "Filtrar por estado de aprobación",
"approvalListEmpty": "No hay aprobaciones",
"approvalState": "Estado de aprobación",
"approvalLoadMore": "Cargar más",
"loadingApprovals": "Cargando aprobaciones",
"approve": "Aprobar",
"approved": "Aprobado",
"denied": "Denegado",
"deniedApproval": "Aprobación denegada",
"all": "Todo",
"deny": "Denegar",
"viewDetails": "Ver detalles",
"requestingNewDeviceApproval": "solicitó un nuevo dispositivo",
"resetFilters": "Reiniciar filtros",
"totalBlocked": "Solicitudes bloqueadas por Pangolin",
"totalRequests": "Solicitudes totales",
@@ -475,9 +566,12 @@
"userSaved": "Usuario guardado",
"userSavedDescription": "El usuario ha sido actualizado.",
"autoProvisioned": "Auto asegurado",
"autoProvisionSettings": "Configuración de Auto Provision",
"autoProvisionedDescription": "Permitir a este usuario ser administrado automáticamente por el proveedor de identidad",
"accessControlsDescription": "Administrar lo que este usuario puede acceder y hacer en la organización",
"accessControlsSubmit": "Guardar controles de acceso",
"singleRolePerUserPlanNotice": "Tu plan sólo soporta un rol por usuario.",
"singleRolePerUserEditionNotice": "Esta edición sólo soporta un rol por usuario.",
"roles": "Roles",
"accessUsersRoles": "Administrar usuarios y roles",
"accessUsersRolesDescription": "Invitar usuarios y añadirlos a roles para administrar el acceso a la organización",
@@ -534,6 +628,8 @@
"targetErrorInvalidPortDescription": "Por favor, introduzca un número de puerto válido",
"targetErrorNoSite": "Ningún sitio seleccionado",
"targetErrorNoSiteDescription": "Por favor, seleccione un sitio para el objetivo",
"targetTargetsCleared": "Objetivos eliminados",
"targetTargetsClearedDescription": "Todos los objetivos han sido eliminados de este recurso",
"targetCreated": "Objetivo creado",
"targetCreatedDescription": "El objetivo se ha creado correctamente",
"targetErrorCreate": "Error al crear el objetivo",
@@ -617,6 +713,7 @@
"resourcesErrorUpdate": "Error al cambiar el recurso",
"resourcesErrorUpdateDescription": "Se ha producido un error al actualizar el recurso",
"access": "Acceder",
"accessControl": "Control de acceso",
"shareLink": "{resource} Compartir Enlace",
"resourceSelect": "Seleccionar recurso",
"shareLinks": "Compartir enlaces",
@@ -729,22 +826,35 @@
"countries": "Países",
"accessRoleCreate": "Crear rol",
"accessRoleCreateDescription": "Crear un nuevo rol para agrupar usuarios y administrar sus permisos.",
"accessRoleEdit": "Editar rol",
"accessRoleEditDescription": "Editar información de rol.",
"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": "Actualizar rol",
"accessRoleUpdated": "Rol actualizado",
"accessRoleUpdatedDescription": "El rol se ha actualizado correctamente.",
"accessApprovalUpdated": "Aprobación procesada",
"accessApprovalApprovedDescription": "Establezca la decisión de Solicitud de Aprobación a aprobar.",
"accessApprovalDeniedDescription": "Define la decisión de Solicitud de Aprobación a denegar.",
"accessRoleErrorUpdate": "Error al actualizar el rol",
"accessRoleErrorUpdateDescription": "Se ha producido un error al actualizar el rol.",
"accessApprovalErrorUpdate": "Error al procesar la aprobación",
"accessApprovalErrorUpdateDescription": "Se ha producido un error al procesar la aprobación.",
"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": "Estás a punto de eliminar el rol `{name}`. No puedes deshacer esta acción.",
"accessRoleRemove": "Quitar rol",
"accessRoleRemoveDescription": "Eliminar un rol de la organización",
"accessRoleRemoveSubmit": "Quitar rol",
"accessRoleRemoved": "Rol eliminado",
"accessRoleRemovedDescription": "El rol se ha eliminado correctamente.",
"accessRoleRequiredRemove": "Antes de eliminar este rol, seleccione un nuevo rol al que transferir miembros existentes.",
"network": "Red",
"manage": "Gestionar",
"sitesNotFound": "Sitios no encontrados.",
"pangolinServerAdmin": "Admin Servidor - Pangolin",
@@ -760,6 +870,9 @@
"sitestCountIncrease": "Aumentar el número de sitios",
"idpManage": "Administrar proveedores de identidad",
"idpManageDescription": "Ver y administrar proveedores de identidad en el sistema",
"idpGlobalModeBanner": "Los proveedores de identidad (IdPs) por organización están deshabilitados en este servidor. Está utilizando IdPs globales (compartidos entre todas las organizaciones). Administra los IdPs globales en el <adminPanelLink>panel de administración</adminPanelLink>. Para habilitar los IdPs por organización, edita la configuración del servidor y establece el modo de IdP en org. <configDocsLink>Consulta la documentación</configDocsLink>. Si deseas seguir utilizando IdPs globales y hacer que esto desaparezca de las configuraciones de la organización, establece explícitamente el modo en global en la configuración.",
"idpGlobalModeBannerUpgradeRequired": "Los proveedores de identidad (IdPs) por organización están deshabilitados en este servidor. Está utilizando IdPs globales (compartidos entre todas las organizaciones). Administra los IdPs globales en el <adminPanelLink>panel de administración</adminPanelLink>. Para usar proveedores de identidad por organización, debes actualizar a la edición Empresarial.",
"idpGlobalModeBannerLicenseRequired": "Los proveedores de identidad (IdPs) por organización están deshabilitados en este servidor. Está utilizando identificadores globales (compartidos en todas las organizaciones). Gestionar identificaciones globales en el panel <adminPanelLink>de administración</adminPanelLink>. Para utilizar proveedores de identidad por organización, se requiere una licencia de empresa.",
"idpDeletedDescription": "Proveedor de identidad eliminado correctamente",
"idpOidc": "OAuth2/OIDC",
"idpQuestionRemove": "¿Está seguro que desea eliminar permanentemente el proveedor de identidad?",
@@ -785,6 +898,7 @@
"idpDisplayName": "Un nombre mostrado para este proveedor de identidad",
"idpAutoProvisionUsers": "Auto-Provisión de Usuarios",
"idpAutoProvisionUsersDescription": "Cuando está habilitado, los usuarios serán creados automáticamente en el sistema al iniciar sesión con la capacidad de asignar a los usuarios a roles y organizaciones.",
"idpAutoProvisionConfigureAfterCreate": "Puede configurar las configuraciones de provisión automática una vez que se haya creado el proveedor de identidad.",
"licenseBadge": "EE",
"idpType": "Tipo de proveedor",
"idpTypeDescription": "Seleccione el tipo de proveedor de identidad que desea configurar",
@@ -836,7 +950,7 @@
"defaultMappingsRole": "Mapeo de Rol por defecto",
"defaultMappingsRoleDescription": "El resultado de esta expresión debe devolver el nombre del rol tal y como se define en la organización como una cadena.",
"defaultMappingsOrg": "Mapeo de organización por defecto",
"defaultMappingsOrgDescription": "Esta expresión debe devolver el ID de org o verdadero para que el usuario pueda acceder a la organización.",
"defaultMappingsOrgDescription": "Cuando se establece, esta expresión debe devolver el ID de la organización o verdadero para que el usuario acceda a esa organización. Cuando no se establece, definir un mapeo de roles es suficiente: se permite la entrada del usuario siempre que se pueda resolver un mapeo de roles válido para él dentro de la organización.",
"defaultMappingsSubmit": "Guardar asignaciones por defecto",
"orgPoliciesEdit": "Editar Política de Organización",
"org": "Organización",
@@ -850,6 +964,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",
@@ -959,7 +1074,7 @@
"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": "Volver a la página principal de acceso",
"signup": "Regístrate",
"loginStart": "Inicia sesión para empezar",
"idpOidcTokenValidating": "Validando token OIDC",
@@ -982,12 +1097,12 @@
"pangolinSetup": "Configuración - Pangolin",
"orgNameRequired": "El nombre de la organización es obligatorio",
"orgIdRequired": "El ID de la organización es obligatorio",
"orgIdMaxLength": "El ID de la organización debe tener como máximo 32 caracteres",
"orgErrorCreate": "Se ha producido un error al crear el org",
"pageNotFound": "Página no encontrada",
"pageNotFoundDescription": "¡Vaya! La página que estás buscando no existe.",
"overview": "Resumen",
"home": "Inicio",
"accessControl": "Control de acceso",
"settings": "Ajustes",
"usersAll": "Todos los usuarios",
"license": "Licencia",
@@ -1050,6 +1165,12 @@
"actionGetUser": "Obtener usuario",
"actionGetOrgUser": "Obtener usuario de la organización",
"actionListOrgDomains": "Listar dominios de la organización",
"actionGetDomain": "Obtener dominio",
"actionCreateOrgDomain": "Crear dominio",
"actionUpdateOrgDomain": "Actualizar dominio",
"actionDeleteOrgDomain": "Eliminar dominio",
"actionGetDNSRecords": "Obtener registros DNS",
"actionRestartOrgDomain": "Reiniciar dominio",
"actionCreateSite": "Crear sitio",
"actionDeleteSite": "Eliminar sitio",
"actionGetSite": "Obtener sitio",
@@ -1061,6 +1182,7 @@
"setupTokenDescription": "Ingrese el token de configuración desde la consola del servidor.",
"setupTokenRequired": "Se requiere el token de configuración",
"actionUpdateSite": "Actualizar sitio",
"actionResetSiteBandwidth": "Restablecer ancho de banda de la organización",
"actionListSiteRoles": "Lista de roles permitidos del sitio",
"actionCreateResource": "Crear Recurso",
"actionDeleteResource": "Eliminar Recurso",
@@ -1090,6 +1212,7 @@
"actionRemoveUser": "Eliminar usuario",
"actionListUsers": "Listar usuarios",
"actionAddUserRole": "Añadir rol de usuario",
"actionSetUserOrgRoles": "Establecer roles de usuario",
"actionGenerateAccessToken": "Generar token de acceso",
"actionDeleteAccessToken": "Eliminar token de acceso",
"actionListAccessTokens": "Lista de Tokens de Acceso",
@@ -1117,6 +1240,10 @@
"actionUpdateIdpOrg": "Actualizar IDP Org",
"actionCreateClient": "Crear cliente",
"actionDeleteClient": "Eliminar cliente",
"actionArchiveClient": "Archivar cliente",
"actionUnarchiveClient": "Desarchivar cliente",
"actionBlockClient": "Bloquear cliente",
"actionUnblockClient": "Desbloquear cliente",
"actionUpdateClient": "Actualizar cliente",
"actionListClients": "Listar clientes",
"actionGetClient": "Obtener cliente",
@@ -1130,17 +1257,18 @@
"actionViewLogs": "Ver registros",
"noneSelected": "Ninguno seleccionado",
"orgNotFound2": "No se encontraron organizaciones.",
"searchProgress": "Buscar...",
"searchPlaceholder": "Buscar...",
"emptySearchOptions": "No se encontraron opciones",
"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": "Ocurrió un error inesperado. Por favor, inténtelo de nuevo.",
"loginRequiredForDevice": "Es necesario iniciar sesión para tu dispositivo.",
"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": "Volver a la contraseña",
"navbar": "Menú de navegación",
"navbarDescription": "Menú de navegación principal para la aplicación",
"navbarDocsLink": "Documentación",
@@ -1188,29 +1316,34 @@
"sidebarOverview": "Resumen",
"sidebarHome": "Inicio",
"sidebarSites": "Sitios",
"sidebarApprovals": "Solicitudes de aprobación",
"sidebarResources": "Recursos",
"sidebarProxyResources": "Público",
"sidebarClientResources": "Privado",
"sidebarAccessControl": "Control de acceso",
"sidebarLogsAndAnalytics": "Registros y análisis",
"sidebarTeam": "Equipo",
"sidebarUsers": "Usuarios",
"sidebarAdmin": "Admin",
"sidebarInvitations": "Invitaciones",
"sidebarRoles": "Roles",
"sidebarShareableLinks": "Enlaces",
"sidebarApiKeys": "Claves API",
"sidebarProvisioning": "Aprovisionamiento",
"sidebarSettings": "Ajustes",
"sidebarAllUsers": "Todos los usuarios",
"sidebarIdentityProviders": "Proveedores de identidad",
"sidebarLicense": "Licencia",
"sidebarClients": "Clientes",
"sidebarUserDevices": "Usuarios",
"sidebarUserDevices": "Dispositivos de usuario",
"sidebarMachineClients": "Máquinas",
"sidebarDomains": "Dominios",
"sidebarGeneral": "Gestionar",
"sidebarLogAndAnalytics": "Registro y análisis",
"sidebarBluePrints": "Planos",
"sidebarOrganization": "Organización",
"sidebarManagement": "Gestión",
"sidebarBillingAndLicenses": "Facturación y licencias",
"sidebarLogsAnalytics": "Analíticas",
"blueprints": "Planos",
"blueprintsDescription": "Aplicar configuraciones declarativas y ver ejecuciones anteriores",
@@ -1232,7 +1365,6 @@
"parsedContents": "Contenido analizado (Sólo lectura)",
"enableDockerSocket": "Habilitar Plano Docker",
"enableDockerSocketDescription": "Activar el raspado de etiquetas de Socket Docker para etiquetas de planos. La ruta del Socket debe proporcionarse a Newt.",
"enableDockerSocketLink": "Saber más",
"viewDockerContainers": "Ver contenedores Docker",
"containersIn": "Contenedores en {siteName}",
"selectContainerDescription": "Seleccione cualquier contenedor para usar como nombre de host para este objetivo. Haga clic en un puerto para usar un puerto.",
@@ -1276,6 +1408,7 @@
"setupErrorCreateAdmin": "Se produjo un error al crear la cuenta de administrador del servidor.",
"certificateStatus": "Estado del certificado",
"loading": "Cargando",
"loadingAnalytics": "Cargando analíticas",
"restart": "Reiniciar",
"domains": "Dominios",
"domainsDescription": "Crear y administrar dominios disponibles en la organización",
@@ -1303,6 +1436,7 @@
"refreshError": "Error al actualizar datos",
"verified": "Verificado",
"pending": "Pendiente",
"pendingApproval": "Pendientes de aprobación",
"sidebarBilling": "Facturación",
"billing": "Facturación",
"orgBillingDescription": "Administrar información de facturación y suscripciones",
@@ -1358,6 +1492,7 @@
"domainPickerNamespace": "Espacio de nombres: {namespace}",
"domainPickerShowMore": "Mostrar más",
"regionSelectorTitle": "Seleccionar Región",
"domainPickerRemoteExitNodeWarning": "Los dominios suministrados no son compatibles cuando los sitios se conectan a nodos de salida remotos. Para que los recursos estén disponibles en nodos remotos, utilice un dominio personalizado en su lugar.",
"regionSelectorInfo": "Seleccionar una región nos ayuda a brindar un mejor rendimiento para tu ubicación. No tienes que estar en la misma región que tu servidor.",
"regionSelectorPlaceholder": "Elige una región",
"regionSelectorComingSoon": "Próximamente",
@@ -1367,10 +1502,11 @@
"billingUsageLimitsOverview": "Descripción general de los límites de uso",
"billingMonitorUsage": "Monitorea tu uso comparado con los límites configurados. Si necesitas que aumenten los límites, contáctanos a soporte@pangolin.net.",
"billingDataUsage": "Uso de datos",
"billingOnlineTime": "Tiempo en línea del sitio",
"billingUsers": "Usuarios activos",
"billingDomains": "Dominios activos",
"billingRemoteExitNodes": "Nodos autogestionados activos",
"billingSites": "Sitios",
"billingUsers": "Usuarios",
"billingDomains": "Dominios",
"billingOrganizations": "Orgánico",
"billingRemoteExitNodes": "Nodos remotos",
"billingNoLimitConfigured": "No se ha configurado ningún límite",
"billingEstimatedPeriod": "Período de facturación estimado",
"billingIncludedUsage": "Uso incluido",
@@ -1395,15 +1531,24 @@
"billingFailedToGetPortalUrl": "Error al obtener la URL del portal",
"billingPortalError": "Error del portal",
"billingDataUsageInfo": "Se le cobran todos los datos transferidos a través de sus túneles seguros cuando se conectan a la nube. Esto incluye tanto tráfico entrante como saliente a través de todos sus sitios. Cuando alcance su límite, sus sitios se desconectarán hasta que actualice su plan o reduzca el uso. Los datos no se cargan cuando se usan nodos.",
"billingOnlineTimeInfo": "Se te cobrará en función del tiempo que tus sitios permanezcan conectados a la nube. Por ejemplo, 44.640 minutos equivale a un sitio que funciona 24/7 durante un mes completo. Cuando alcance su límite, sus sitios se desconectarán hasta que mejore su plan o reduzca el uso. No se cargará el tiempo al usar nodos.",
"billingUsersInfo": "Se le cobra por cada usuario en la organización. La facturación se calcula diariamente según el número de cuentas de usuario activas en su órgano.",
"billingDomainInfo": "Se le cobra por cada dominio en la organización. La facturación se calcula diariamente en función del número de cuentas de dominio activas en su órgano.",
"billingRemoteExitNodesInfo": "Se le cobra por cada nodo administrado en la organización. La facturación se calcula diariamente en función del número de nodos activos gestionados en su órgano.",
"billingSInfo": "Cuántos sitios puedes usar",
"billingUsersInfo": "Cuántos usuarios puedes usar",
"billingDomainInfo": "Cuántos dominios puedes usar",
"billingRemoteExitNodesInfo": "Cuántos nodos remotos puedes usar",
"billingLicenseKeys": "Claves de licencia",
"billingLicenseKeysDescription": "Administrar las suscripciones de su clave de licencia",
"billingLicenseSubscription": "Suscripción de licencia",
"billingInactive": "Inactivo",
"billingLicenseItem": "Licencia",
"billingQuantity": "Cantidad",
"billingTotal": "total",
"billingModifyLicenses": "Modificar suscripción de licencia",
"domainNotFound": "Dominio no encontrado",
"domainNotFoundDescription": "Este recurso está deshabilitado porque el dominio ya no existe en nuestro sistema. Por favor, establece un nuevo dominio para este recurso.",
"failed": "Fallido",
"createNewOrgDescription": "Crear una nueva organización",
"organization": "Organización",
"primary": "Principal",
"port": "Puerto",
"securityKeyManage": "Gestionar llaves de seguridad",
"securityKeyDescription": "Agregar o eliminar llaves de seguridad para autenticación sin contraseña",
@@ -1419,7 +1564,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": "Usar clave de seguridad",
"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...",
@@ -1475,11 +1620,47 @@
"resourcePortRequired": "Se requiere número de puerto para recursos no HTTP",
"resourcePortNotAllowed": "El número de puerto no debe establecerse para recursos HTTP",
"billingPricingCalculatorLink": "Calculadora de Precios",
"billingYourPlan": "Su plan",
"billingViewOrModifyPlan": "Ver o modificar su plan actual",
"billingViewPlanDetails": "Ver detalles del plan",
"billingUsageAndLimits": "Uso y límites",
"billingViewUsageAndLimits": "Ver los límites de tu plan y el uso actual",
"billingCurrentUsage": "Uso actual",
"billingMaximumLimits": "Límites máximos",
"billingRemoteNodes": "Nodos remotos",
"billingUnlimited": "Ilimitado",
"billingPaidLicenseKeys": "Claves de licencia pagadas",
"billingManageLicenseSubscription": "Administra tu suscripción para las claves de licencia autoalojadas pagadas",
"billingCurrentKeys": "Claves actuales",
"billingModifyCurrentPlan": "Modificar plan actual",
"billingConfirmUpgrade": "Confirmar actualización",
"billingConfirmDowngrade": "Confirmar descenso",
"billingConfirmUpgradeDescription": "Estás a punto de actualizar tu plan. Revisa los nuevos límites y precios a continuación.",
"billingConfirmDowngradeDescription": "Está a punto de rebajar su plan. Revise los nuevos límites y los precios a continuación.",
"billingPlanIncludes": "Plan Incluye",
"billingProcessing": "Procesando...",
"billingConfirmUpgradeButton": "Confirmar actualización",
"billingConfirmDowngradeButton": "Confirmar descenso",
"billingLimitViolationWarning": "El uso excede los nuevos límites del plan",
"billingLimitViolationDescription": "Su uso actual excede los límites de este plan. Después de degradar, todas las acciones se desactivarán hasta que reduzca el uso dentro de los nuevos límites. Por favor, revisa las siguientes características que están actualmente por encima de los límites. Límites en violación:",
"billingFeatureLossWarning": "Aviso de disponibilidad de funcionalidad",
"billingFeatureLossDescription": "Al degradar, las características no disponibles en el nuevo plan se desactivarán automáticamente. Algunas configuraciones y configuraciones pueden perderse. Por favor, revise la matriz de precios para entender qué características ya no estarán disponibles.",
"billingUsageExceedsLimit": "El uso actual ({current}) supera el límite ({limit})",
"billingPastDueTitle": "Pago vencido",
"billingPastDueDescription": "Su pago ha vencido. Por favor, actualice su método de pago para seguir utilizando las características actuales de su plan. Si no se resuelve, tu suscripción se cancelará y serás revertido al nivel gratuito.",
"billingUnpaidTitle": "Suscripción no pagada",
"billingUnpaidDescription": "Tu suscripción no está pagada y has sido revertido al nivel gratuito. Por favor, actualiza tu método de pago para restaurar tu suscripción.",
"billingIncompleteTitle": "Pago incompleto",
"billingIncompleteDescription": "Su pago está incompleto. Por favor, complete el proceso de pago para activar su suscripción.",
"billingIncompleteExpiredTitle": "Pago expirado",
"billingIncompleteExpiredDescription": "Tu pago nunca se completó y ha expirado. Has sido revertido al nivel gratuito. Suscríbete de nuevo para restaurar el acceso a las funciones de pago.",
"billingManageSubscription": "Administra tu suscripción",
"billingResolvePaymentIssue": "Por favor resuelva su problema de pago antes de actualizar o bajar de calificación",
"signUpTerms": {
"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 +1727,26 @@
"IntervalSeconds": "Intervalo Saludable",
"timeoutSeconds": "Tiempo agotado (seg)",
"timeIsInSeconds": "El tiempo está en segundos",
"requireDeviceApproval": "Requiere aprobaciones del dispositivo",
"requireDeviceApprovalDescription": "Los usuarios con este rol necesitan nuevos dispositivos aprobados por un administrador antes de poder conectarse y acceder a los recursos.",
"sshAccess": "Acceso a SSH",
"roleAllowSsh": "Permitir SSH",
"roleAllowSshAllow": "Permitir",
"roleAllowSshDisallow": "Rechazar",
"roleAllowSshDescription": "Permitir a los usuarios con este rol conectarse a recursos a través de SSH. Cuando está desactivado, el rol no puede usar acceso SSH.",
"sshSudoMode": "Acceso Sudo",
"sshSudoModeNone": "Ninguna",
"sshSudoModeNoneDescription": "El usuario no puede ejecutar comandos con sudo.",
"sshSudoModeFull": "Sudo completo",
"sshSudoModeFullDescription": "El usuario puede ejecutar cualquier comando con sudo.",
"sshSudoModeCommands": "Comandos",
"sshSudoModeCommandsDescription": "El usuario sólo puede ejecutar los comandos especificados con sudo.",
"sshSudo": "Permitir sudo",
"sshSudoCommands": "Comandos Sudo",
"sshSudoCommandsDescription": "Lista separada por comas de comandos que el usuario puede ejecutar con sudo.",
"sshCreateHomeDir": "Crear directorio principal",
"sshUnixGroups": "Grupos Unix",
"sshUnixGroupsDescription": "Grupos Unix separados por comas para agregar el usuario en el host de destino.",
"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.",
@@ -1586,6 +1787,8 @@
"resourcesTableNoInternalResourcesFound": "No se encontraron recursos internos.",
"resourcesTableDestination": "Destino",
"resourcesTableAlias": "Alias",
"resourcesTableAliasAddress": "Dirección del alias",
"resourcesTableAliasAddressInfo": "Esta dirección es parte de la subred de utilidad de la organización. Se utiliza para resolver registros de alias usando resolución DNS interna.",
"resourcesTableClients": "Clientes",
"resourcesTableAndOnlyAccessibleInternally": "y solo son accesibles internamente cuando se conectan con un cliente.",
"resourcesTableNoTargets": "Sin objetivos",
@@ -1752,6 +1955,40 @@
"exitNode": "Nodo de Salida",
"country": "País",
"rulesMatchCountry": "Actualmente basado en IP de origen",
"region": "Región",
"selectRegion": "Seleccionar región",
"searchRegions": "Buscar regiones...",
"noRegionFound": "Región no encontrada.",
"rulesMatchRegion": "Seleccione una agrupación regional de países",
"rulesErrorInvalidRegion": "Región no válida",
"rulesErrorInvalidRegionDescription": "Por favor, seleccione una región válida.",
"regionAfrica": "Africa",
"regionNorthernAfrica": "África septentrional",
"regionEasternAfrica": "África oriental",
"regionMiddleAfrica": "África central",
"regionSouthernAfrica": "África del Sur",
"regionWesternAfrica": "África Occidental",
"regionAmericas": "Américas",
"regionCaribbean": "Caribe",
"regionCentralAmerica": "América Central",
"regionSouthAmerica": "América del Sur",
"regionNorthernAmerica": "América del Norte",
"regionAsia": "Asia",
"regionCentralAsia": "Asia Central",
"regionEasternAsia": "Asia oriental",
"regionSouthEasternAsia": "Asia sudoriental",
"regionSouthernAsia": "Asia meridional",
"regionWesternAsia": "Asia Occidental",
"regionEurope": "Europa",
"regionEasternEurope": "Europa del Este",
"regionNorthernEurope": "Europa septentrional",
"regionSouthernEurope": "Europa meridional",
"regionWesternEurope": "Europa Occidental",
"regionOceania": "Oceania",
"regionAustraliaAndNewZealand": "Australia y Nueva Zelanda",
"regionMelanesia": "Melanesia",
"regionMicronesia": "Micronesia",
"regionPolynesia": "Polynesia",
"managedSelfHosted": {
"title": "Autogestionado",
"description": "Servidor Pangolin autoalojado más fiable y de bajo mantenimiento con campanas y silbidos extra",
@@ -1790,7 +2027,7 @@
},
"internationaldomaindetected": "Dominio Internacional detectado",
"willbestoredas": "Se almacenará como:",
"roleMappingDescription": "Determinar cómo se asignan los roles a los usuarios cuando se registran cuando está habilitada la provisión automática.",
"roleMappingDescription": "Determine cómo se asignan los roles a los usuarios cuando inician sesión con este proveedor de identidad.",
"selectRole": "Seleccione un rol",
"roleMappingExpression": "Expresión",
"selectRolePlaceholder": "Elija un rol",
@@ -1800,6 +2037,25 @@
"invalidValue": "Valor inválido",
"idpTypeLabel": "Tipo de proveedor de identidad",
"roleMappingExpressionPlaceholder": "e.g., contiene(grupos, 'administrador') && 'administrador' || 'miembro'",
"roleMappingModeFixedRoles": "Roles fijos",
"roleMappingModeMappingBuilder": "Constructor de mapeo",
"roleMappingModeRawExpression": "Expresión sin procesar",
"roleMappingFixedRolesPlaceholderSelect": "Seleccione uno o más roles",
"roleMappingFixedRolesPlaceholderFreeform": "Nombre de rol de tipo (coincidencia exacta por organización)",
"roleMappingFixedRolesDescriptionSameForAll": "Asignar el mismo rol establecido a cada usuario auto-provisionado.",
"roleMappingFixedRolesDescriptionDefaultPolicy": "Para las políticas predeterminadas, escriba nombres de roles que existen en cada organización donde los usuarios son proporcionados. Los nombres deben coincidir exactamente.",
"roleMappingClaimPath": "Reclamar ruta",
"roleMappingClaimPathPlaceholder": "grupos",
"roleMappingClaimPathDescription": "Ruta en el payload del token que contiene valores de origen (por ejemplo, grupos).",
"roleMappingMatchValue": "Valor de partida",
"roleMappingAssignRoles": "Asignar roles",
"roleMappingAddMappingRule": "Añadir regla de mapeo",
"roleMappingRawExpressionResultDescription": "La expresión debe evaluar a un array de cadenas o cadenas.",
"roleMappingRawExpressionResultDescriptionSingleRole": "La expresión debe evaluar una cadena (un solo nombre de rol).",
"roleMappingMatchValuePlaceholder": "Valor coincidente (por ejemplo: admin)",
"roleMappingAssignRolesPlaceholderFreeform": "Escriba nombres de rol (exacto por org)",
"roleMappingBuilderFreeformRowHint": "Los nombres de rol deben coincidir con un rol en cada organización objetivo.",
"roleMappingRemoveRule": "Eliminar",
"idpGoogleConfiguration": "Configuración de Google",
"idpGoogleConfigurationDescription": "Configurar las credenciales de Google OAuth2",
"idpGoogleClientIdDescription": "Google OAuth2 Client ID",
@@ -1836,6 +2092,9 @@
"authPageBrandingQuestionRemove": "¿Está seguro de que desea eliminar la marca de las páginas de autenticación?",
"authPageBrandingDeleteConfirm": "Confirmar eliminación de la marca",
"brandingLogoURL": "URL del logotipo",
"brandingLogoURLOrPath": "URL o ruta de Logo",
"brandingLogoPathDescription": "Introduzca una URL o una ruta local.",
"brandingLogoURLDescription": "Introduzca una URL de acceso público a su imagen de logotipo.",
"brandingPrimaryColor": "Color primario",
"brandingLogoWidth": "Ancho (px)",
"brandingLogoHeight": "Altura (px)",
@@ -1860,8 +2119,10 @@
"selectDomainForOrgAuthPage": "Seleccione un dominio para la página de autenticación de la organización",
"domainPickerProvidedDomain": "Dominio proporcionado",
"domainPickerFreeProvidedDomain": "Dominio proporcionado gratis",
"domainPickerFreeDomainsPaidFeature": "Los dominios proporcionados son una función de pago. Suscríbete para obtener un dominio incluido con tu plan — no necesitas traer el tuyo propio.",
"domainPickerVerified": "Verificado",
"domainPickerUnverified": "Sin verificar",
"domainPickerManual": "Manual",
"domainPickerInvalidSubdomainStructure": "Este subdominio contiene caracteres o estructura no válidos. Se limpiará automáticamente al guardar.",
"domainPickerError": "Error",
"domainPickerErrorLoadDomains": "Error al cargar los dominios de la organización",
@@ -1885,6 +2146,13 @@
"orgAuthBackToSignIn": "Volver a iniciar sesión estándar",
"orgAuthNoAccount": "¿No tienes una cuenta?",
"subscriptionRequiredToUse": "Se requiere una suscripción para utilizar esta función.",
"mustUpgradeToUse": "Debes actualizar tu suscripción para usar esta función.",
"subscriptionRequiredTierToUse": "Esta función requiere <tierLink>{tier}</tierLink> o superior.",
"upgradeToTierToUse": "Actualiza a <tierLink>{tier}</tierLink> o superior para usar esta función.",
"subscriptionTierTier1": "Inicio",
"subscriptionTierTier2": "Equipo",
"subscriptionTierTier3": "Negocio",
"subscriptionTierEnterprise": "Empresa",
"idpDisabled": "Los proveedores de identidad están deshabilitados.",
"orgAuthPageDisabled": "La página de autenticación de la organización está deshabilitada.",
"domainRestartedDescription": "Verificación de dominio reiniciada con éxito",
@@ -2072,6 +2340,32 @@
}
}
},
"newPricingLicenseForm": {
"title": "Obtener una licencia",
"description": "Elige un plan y dinos cómo planeas usar Pangolin.",
"chooseTier": "Elige tu plan",
"viewPricingLink": "Ver precios, características y límites",
"tiers": {
"starter": {
"title": "Interruptor",
"description": "Características de la empresa, 25 usuarios, 25 sitios y soporte comunitario."
},
"scale": {
"title": "Escala",
"description": "Funcionalidades empresariales, 50 usuarios, 100 sitios y soporte prioritario."
}
},
"personalUseOnly": "Solo uso personal (licencia gratuita - sin salida)",
"buttons": {
"continueToCheckout": "Continuar con el pago"
},
"toasts": {
"checkoutError": {
"title": "Error de pago",
"description": "No se pudo iniciar el pago. Por favor, inténtelo de nuevo."
}
}
},
"priority": "Prioridad",
"priorityDescription": "Las rutas de prioridad más alta son evaluadas primero. Prioridad = 100 significa orden automático (decisiones del sistema). Utilice otro número para hacer cumplir la prioridad manual.",
"instanceName": "Nombre de instancia",
@@ -2122,7 +2416,7 @@
"action": "Accin",
"actor": "Actor",
"timestamp": "Timestamp",
"accessLogs": "Registros de acceso",
"accessLogs": "Authentication Logs",
"exportCsv": "Exportar CSV",
"exportError": "Error desconocido al exportar CSV",
"exportCsvTooltip": "Dentro del rango de tiempo",
@@ -2142,24 +2436,26 @@
"noMoreAuthMethods": "No Valid Auth",
"ip": "IP",
"reason": "Razón",
"requestLogs": "Registros de Solicitud",
"requestLogs": "HTTPS Request Logs",
"requestAnalytics": "Analítica de Solicitud",
"host": "Anfitrión",
"location": "Ubicación",
"actionLogs": "Registros de acción",
"sidebarLogsRequest": "Registros de Solicitud",
"sidebarLogsAccess": "Registros de acceso",
"sidebarLogsAction": "Registros de acción",
"actionLogs": "Admin Action Logs",
"sidebarLogsRequest": "HTTPS Request Logs",
"sidebarLogsAccess": "Authentication Logs",
"sidebarLogsAction": "Admin Action Logs",
"logRetention": "Retención de Log",
"logRetentionDescription": "Administrar cuánto tiempo se conservan los diferentes tipos de registros para esta organización o desactivarlos",
"requestLogsDescription": "Ver registros de solicitudes detallados para los recursos de esta organización",
"requestLogsDescription": "View detailed request logs for HTTPS resources in this organization",
"requestAnalyticsDescription": "Ver análisis de solicitudes detalladas de recursos en esta organización",
"logRetentionRequestLabel": "Retención de Registro de Solicitud",
"logRetentionRequestLabel": "HTTPS Request Log Retention",
"logRetentionRequestDescription": "Cuánto tiempo conservar los registros de solicitudes",
"logRetentionAccessLabel": "Retención de Log de Acceso",
"logRetentionAccessLabel": "Authentication Log Retention",
"logRetentionAccessDescription": "Cuánto tiempo retener los registros de acceso",
"logRetentionActionLabel": "Retención de registro de acción",
"logRetentionActionLabel": "Admin Action Log Retention",
"logRetentionActionDescription": "Cuánto tiempo retener los registros de acción",
"logRetentionConnectionLabel": "Network Log Retention",
"logRetentionConnectionDescription": "Cuánto tiempo conservar los registros de conexión",
"logRetentionDisabled": "Deshabilitado",
"logRetention3Days": "3 días",
"logRetention7Days": "7 días",
@@ -2170,7 +2466,15 @@
"logRetentionEndOfFollowingYear": "Fin del año siguiente",
"actionLogsDescription": "Ver un historial de acciones realizadas en esta organización",
"accessLogsDescription": "Ver solicitudes de acceso a los recursos de esta organización",
"licenseRequiredToUse": "Se requiere una licencia Enterprise para utilizar esta función.",
"connectionLogs": "Network Logs",
"connectionLogsDescription": "View network session logs handled by sites in this organization",
"sidebarLogsConnection": "Network Logs",
"sidebarLogsStreaming": "Event Streaming",
"sourceAddress": "Dirección de origen",
"destinationAddress": "Dirección de destino",
"duration": "Duración",
"licenseRequiredToUse": "Se requiere una licencia <enterpriseLicenseLink>Enterprise Edition</enterpriseLicenseLink> o <pangolinCloudLink>Pangolin Cloud</pangolinCloudLink> para usar esta función. <bookADemoLink>Reserve una demostración o prueba POC</bookADemoLink>.",
"ossEnterpriseEditionRequired": "La <enterpriseEditionLink>Enterprise Edition</enterpriseEditionLink> es necesaria para utilizar esta función. Esta función también está disponible en <pangolinCloudLink>Pangolin Cloud</pangolinCloudLink>. <bookADemoLink>Reserva una demostración o prueba POC</bookADemoLink>.",
"certResolver": "Resolver certificado",
"certResolverDescription": "Seleccione la resolución de certificados a utilizar para este recurso.",
"selectCertResolver": "Seleccionar Resolver Certificado",
@@ -2231,6 +2535,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": "Validando código de dispositivo...",
"deviceCodeVerifying": "Verificando autorización del dispositivo...",
"signedInAs": "Conectado como",
"deviceCodeEnterPrompt": "Introduzca el código mostrado en el dispositivo",
"continue": "Continuar",
@@ -2243,7 +2549,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": "El dispositivo está autorizado para acceder a su cuenta. Por favor, vuelva a la aplicación cliente.",
"pangolinCloud": "Nube de Pangolin",
"viewDevices": "Ver dispositivos",
"viewDevicesDescription": "Administra tus dispositivos conectados",
@@ -2305,10 +2611,14 @@
"identifier": "Identifier",
"deviceLoginUseDifferentAccount": "¿No tú? Utilice una cuenta diferente.",
"deviceLoginDeviceRequestingAccessToAccount": "Un dispositivo está solicitando acceso a esta cuenta.",
"loginSelectAuthenticationMethod": "Seleccione un método de autenticación para continuar.",
"noData": "Sin datos",
"machineClients": "Clientes de la máquina",
"install": "Instalar",
"run": "Ejecutar",
"envFile": "Archivo de Entorno",
"serviceFile": "Archivo de Servicio",
"enableAndStart": "Habilitar y empezar",
"clientNameDescription": "El nombre mostrado del cliente que se puede cambiar más adelante.",
"clientAddress": "Dirección del cliente (Avanzado)",
"setupFailedToFetchSubnet": "No se pudo obtener la subred por defecto",
@@ -2349,6 +2659,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",
@@ -2363,6 +2674,17 @@
"editInternalResourceDialogAccessControl": "Control de acceso",
"editInternalResourceDialogAccessControlDescription": "Controla qué roles, usuarios y clientes de máquinas tienen acceso a este recurso cuando están conectados. Los administradores siempre tienen acceso.",
"editInternalResourceDialogPortRangeValidationError": "El rango de puertos debe ser \"*\" para todos los puertos, o una lista separada por comas de puertos y rangos (por ejemplo, \"80,443,8000-9000\"). Los puertos deben estar entre 1 y 65535.",
"internalResourceAuthDaemonStrategy": "Ubicación del demonio de autenticación SSSH",
"internalResourceAuthDaemonStrategyDescription": "Elija dónde se ejecuta el daemon de autenticación SSH: en el sitio (Newt) o en un host remoto.",
"internalResourceAuthDaemonDescription": "El daemon de autenticación SSSH maneja la firma de claves SSH y autenticación PAM para este recurso. Elija si se ejecuta en el sitio (Newt) o en un host remoto separado. Vea <docsLink>la documentación</docsLink> para más.",
"internalResourceAuthDaemonDocsUrl": "https://docs.pangolin.net",
"internalResourceAuthDaemonStrategyPlaceholder": "Seleccionar estrategia",
"internalResourceAuthDaemonStrategyLabel": "Ubicación",
"internalResourceAuthDaemonSite": "En el sitio",
"internalResourceAuthDaemonSiteDescription": "Auth daemon corre en el sitio (Newt).",
"internalResourceAuthDaemonRemote": "Host remoto",
"internalResourceAuthDaemonRemoteDescription": "El daemon Auth corre en un host que no es el sitio.",
"internalResourceAuthDaemonPort": "Puerto de demonio (opcional)",
"orgAuthWhatsThis": "¿Dónde puedo encontrar el ID de mi organización?",
"learnMore": "Más información",
"backToHome": "Volver a inicio",
@@ -2392,5 +2714,209 @@
"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": "Disponible",
"archived": "Archivado",
"noArchivedDevices": "No se encontraron dispositivos archivados",
"deviceArchived": "Dispositivo archivado",
"deviceArchivedDescription": "El dispositivo se ha archivado correctamente.",
"errorArchivingDevice": "Error al archivar dispositivo",
"failedToArchiveDevice": "Error al archivar el dispositivo",
"deviceQuestionArchive": "¿Está seguro que desea archivar este dispositivo?",
"deviceMessageArchive": "El dispositivo será archivado y eliminado de su lista de dispositivos activos.",
"deviceArchiveConfirm": "Archivar dispositivo",
"archiveDevice": "Archivar dispositivo",
"archive": "Archivar",
"deviceUnarchived": "Dispositivo desarchivado",
"deviceUnarchivedDescription": "El dispositivo se ha desarchivado correctamente.",
"errorUnarchivingDevice": "Error al desarchivar dispositivo",
"failedToUnarchiveDevice": "Error al desarchivar el dispositivo",
"unarchive": "Desarchivar",
"archiveClient": "Archivar cliente",
"archiveClientQuestion": "¿Está seguro que desea archivar este cliente?",
"archiveClientMessage": "El cliente será archivado y eliminado de su lista de clientes activos.",
"archiveClientConfirm": "Archivar cliente",
"blockClient": "Bloquear cliente",
"blockClientQuestion": "¿Estás seguro de que quieres bloquear a este cliente?",
"blockClientMessage": "El dispositivo será forzado a desconectarse si está conectado actualmente. Puede desbloquear el dispositivo más tarde.",
"blockClientConfirm": "Bloquear cliente",
"active": "Activo",
"usernameOrEmail": "Nombre de usuario o email",
"selectYourOrganization": "Seleccione su organización",
"signInTo": "Iniciar sesión en",
"signInWithPassword": "Continuar con la contraseña",
"noAuthMethodsAvailable": "No hay métodos de autenticación disponibles para esta organización.",
"enterPassword": "Introduzca su contraseña",
"enterMfaCode": "Introduzca el código de su aplicación de autenticación",
"securityKeyRequired": "Utilice su clave de seguridad para iniciar sesión.",
"needToUseAnotherAccount": "¿Necesitas usar una cuenta diferente?",
"loginLegalDisclaimer": "Al hacer clic en los botones de abajo, reconoces que has leído, comprendido, y acepta los <termsOfService>Términos de Servicio</termsOfService> y <privacyPolicy>Política de Privacidad</privacyPolicy>.",
"termsOfService": "Términos de Servicio",
"privacyPolicy": "Política de privacidad",
"userNotFoundWithUsername": "Ningún usuario encontrado con ese nombre de usuario.",
"verify": "Verificar",
"signIn": "Iniciar sesión",
"forgotPassword": "¿Olvidaste la contraseña?",
"orgSignInTip": "Si has iniciado sesión antes, puedes introducir tu nombre de usuario o correo electrónico arriba para autenticarte con el proveedor de identidad de tu organización. ¡Es más fácil!",
"continueAnyway": "Continuar de todos modos",
"dontShowAgain": "No volver a mostrar",
"orgSignInNotice": "¿Sabía usted?",
"signupOrgNotice": "¿Intentando iniciar sesión?",
"signupOrgTip": "¿Estás intentando iniciar sesión a través del proveedor de identidad de tu organización?",
"signupOrgLink": "Inicia sesión o regístrate con tu organización",
"verifyEmailLogInWithDifferentAccount": "Usar una cuenta diferente",
"logIn": "Iniciar sesión",
"deviceInformation": "Información del dispositivo",
"deviceInformationDescription": "Información sobre el dispositivo y el agente",
"deviceSecurity": "Seguridad del dispositivo",
"deviceSecurityDescription": "Información de postura de seguridad del dispositivo",
"platform": "Plataforma",
"macosVersion": "versión macOS",
"windowsVersion": "Versión de Windows",
"iosVersion": "Versión de iOS",
"androidVersion": "Versión de Android",
"osVersion": "Versión del SO",
"kernelVersion": "Versión de Kernel",
"deviceModel": "Modelo de dispositivo",
"serialNumber": "Número Serial",
"hostname": "Hostname",
"firstSeen": "Primer detectado",
"lastSeen": "Último Visto",
"biometricsEnabled": "Biometría habilitada",
"diskEncrypted": "Disco cifrado",
"firewallEnabled": "Cortafuegos activado",
"autoUpdatesEnabled": "Actualizaciones automáticas habilitadas",
"tpmAvailable": "TPM disponible",
"windowsAntivirusEnabled": "Antivirus activado",
"macosSipEnabled": "Protección de integridad del sistema (SIP)",
"macosGatekeeperEnabled": "Gatekeeper",
"macosFirewallStealthMode": "Modo Sigilo Firewall",
"linuxAppArmorEnabled": "AppArmor",
"linuxSELinuxEnabled": "SELinux",
"deviceSettingsDescription": "Ver información y ajustes del dispositivo",
"devicePendingApprovalDescription": "Este dispositivo está esperando su aprobación",
"deviceBlockedDescription": "Este dispositivo está actualmente bloqueado. No podrá conectarse a ningún recurso a menos que sea desbloqueado.",
"unblockClient": "Desbloquear cliente",
"unblockClientDescription": "El dispositivo ha sido desbloqueado",
"unarchiveClient": "Desarchivar cliente",
"unarchiveClientDescription": "El dispositivo ha sido desarchivado",
"block": "Bloque",
"unblock": "Desbloquear",
"deviceActions": "Acciones del dispositivo",
"deviceActionsDescription": "Administrar estado y acceso al dispositivo",
"devicePendingApprovalBannerDescription": "Este dispositivo está pendiente de aprobación. No podrá conectarse a recursos hasta que sea aprobado.",
"connected": "Conectado",
"disconnected": "Desconectado",
"approvalsEmptyStateTitle": "Aprobaciones de dispositivo no habilitadas",
"approvalsEmptyStateDescription": "Habilita las aprobaciones de dispositivos para que los roles requieran aprobación del administrador antes de que los usuarios puedan conectar nuevos dispositivos.",
"approvalsEmptyStateStep1Title": "Ir a roles",
"approvalsEmptyStateStep1Description": "Navega a la configuración de roles de tu organización para configurar las aprobaciones de dispositivos.",
"approvalsEmptyStateStep2Title": "Habilitar aprobaciones de dispositivo",
"approvalsEmptyStateStep2Description": "Editar un rol y habilitar la opción 'Requerir aprobaciones de dispositivos'. Los usuarios con este rol necesitarán la aprobación del administrador para nuevos dispositivos.",
"approvalsEmptyStatePreviewDescription": "Vista previa: Cuando está habilitado, las solicitudes de dispositivo pendientes aparecerán aquí para su revisión",
"approvalsEmptyStateButtonText": "Administrar roles",
"domainErrorTitle": "Estamos teniendo problemas para verificar su dominio",
"idpAdminAutoProvisionPoliciesTabHint": "Configure el mapeo de roles y las políticas de organización en la pestaña <policiesTabLink>Configuración de provisión automática</policiesTabLink>.",
"streamingTitle": "Transmisión de Eventos",
"streamingDescription": "Transmita eventos desde su organización a destinos externos en tiempo real.",
"streamingUnnamedDestination": "Destino sin nombre",
"streamingNoUrlConfigured": "No hay URL configurada",
"streamingAddDestination": "Añadir destino",
"streamingHttpWebhookTitle": "Webhook HTTP",
"streamingHttpWebhookDescription": "Enviar eventos a cualquier extremo HTTP con autenticación flexible y plantilla.",
"streamingS3Title": "Amazon S3",
"streamingS3Description": "Transmite eventos a un bucket de almacenamiento de objetos compatible con S3. Próximamente.",
"streamingDatadogTitle": "Datadog",
"streamingDatadogDescription": "Reenviar eventos directamente a tu cuenta de Datadog. Próximamente.",
"streamingTypePickerDescription": "Elija un tipo de destino para empezar.",
"streamingFailedToLoad": "Error al cargar destinos",
"streamingUnexpectedError": "Se ha producido un error inesperado.",
"streamingFailedToUpdate": "Error al actualizar destino",
"streamingDeletedSuccess": "Destino eliminado correctamente",
"streamingFailedToDelete": "Error al eliminar destino",
"streamingDeleteTitle": "Eliminar destino",
"streamingDeleteButtonText": "Eliminar destino",
"streamingDeleteDialogAreYouSure": "¿Está seguro que desea eliminar",
"streamingDeleteDialogThisDestination": "este destino",
"streamingDeleteDialogPermanentlyRemoved": "? Toda la configuración se eliminará permanentemente.",
"httpDestEditTitle": "Editar destino",
"httpDestAddTitle": "Añadir destino HTTP",
"httpDestEditDescription": "Actualizar la configuración para este destino de transmisión de eventos HTTP.",
"httpDestAddDescription": "Configure un nuevo extremo HTTP para recibir los eventos de su organización.",
"httpDestTabSettings": "Ajustes",
"httpDestTabHeaders": "Encabezados",
"httpDestTabBody": "Cuerpo",
"httpDestTabLogs": "Registros",
"httpDestNamePlaceholder": "Mi destino HTTP",
"httpDestUrlLabel": "URL de destino",
"httpDestUrlErrorHttpRequired": "URL debe usar http o https",
"httpDestUrlErrorHttpsRequired": "HTTPS es necesario en implementaciones en la nube",
"httpDestUrlErrorInvalid": "Introduzca una URL válida (ej. https://example.com/webhook)",
"httpDestAuthTitle": "Autenticación",
"httpDestAuthDescription": "Elija cómo están autenticadas las solicitudes en su punto final.",
"httpDestAuthNoneTitle": "Sin autenticación",
"httpDestAuthNoneDescription": "Envía solicitudes sin un encabezado de autorización.",
"httpDestAuthBearerTitle": "Tóken de portador",
"httpDestAuthBearerDescription": "Añade un encabezado Authorization: Bearer '<token>' a cada solicitud.",
"httpDestAuthBearerPlaceholder": "Tu clave o token API",
"httpDestAuthBasicTitle": "Auth Básica",
"httpDestAuthBasicDescription": "Añade un encabezado Authorization: Basic '<credenciales>'. Proporcione las credenciales como nombredeusuario:contraseña.",
"httpDestAuthBasicPlaceholder": "usuario:contraseña",
"httpDestAuthCustomTitle": "Cabecera personalizada",
"httpDestAuthCustomDescription": "Especifique un nombre de cabecera HTTP personalizado y un valor para la autenticación (por ejemplo, X-API-Key).",
"httpDestAuthCustomHeaderNamePlaceholder": "Nombre de cabecera (ej. X-API-Key)",
"httpDestAuthCustomHeaderValuePlaceholder": "Valor de cabecera",
"httpDestCustomHeadersTitle": "Cabeceras HTTP personalizadas",
"httpDestCustomHeadersDescription": "Añadir cabeceras personalizadas a cada petición saliente. Útil para tokens estáticos o un tipo de contenido personalizado. De forma predeterminada, Content Type: application/json es enviado.",
"httpDestNoHeadersConfigured": "No hay cabeceras personalizadas. Haga clic en \"Añadir cabecera\" para añadir una.",
"httpDestHeaderNamePlaceholder": "Nombre de cabecera",
"httpDestHeaderValuePlaceholder": "Valor",
"httpDestAddHeader": "Añadir cabecera",
"httpDestBodyTemplateTitle": "Plantilla de cuerpo personalizada",
"httpDestBodyTemplateDescription": "Controla la estructura de carga de JSON enviada a tu punto final. Si está desactivado, se envía un objeto JSON por defecto para cada evento.",
"httpDestEnableBodyTemplate": "Activar plantilla de cuerpo personalizado",
"httpDestBodyTemplateLabel": "Plantilla de cuerpo (JSON)",
"httpDestBodyTemplateHint": "Utilice variables de plantilla para referenciar los campos del evento en su carga útil.",
"httpDestPayloadFormatTitle": "Formato de carga",
"httpDestPayloadFormatDescription": "Cómo se serializan los eventos en cada cuerpo de solicitud.",
"httpDestFormatJsonArrayTitle": "Matriz JSON",
"httpDestFormatJsonArrayDescription": "Una petición por lote, cuerpo es una matriz JSON. Compatible con la mayoría de los webhooks y Datadog.",
"httpDestFormatNdjsonTitle": "NDJSON",
"httpDestFormatNdjsonDescription": "Una petición por lote, el cuerpo es JSON delimitado por línea — un objeto por línea, sin arrays externos. Requerido por Splunk HEC, Elastic / OpenSearch, y Grafana Loki.",
"httpDestFormatSingleTitle": "Un evento por solicitud",
"httpDestFormatSingleDescription": "Envía un HTTP POST separado para cada evento individual. Úsalo sólo para los extremos que no pueden manejar lotes.",
"httpDestLogTypesTitle": "Tipos de Log",
"httpDestLogTypesDescription": "Elija qué tipos de registro son reenviados a este destino. Sólo los tipos de registro habilitados serán transmitidos.",
"httpDestAccessLogsTitle": "Authentication Logs",
"httpDestAccessLogsDescription": "Intentos de acceso a recursos, incluyendo solicitudes autenticadas y denegadas.",
"httpDestActionLogsTitle": "Admin Action Logs",
"httpDestActionLogsDescription": "Acciones administrativas realizadas por los usuarios dentro de la organización.",
"httpDestConnectionLogsTitle": "Network Logs",
"httpDestConnectionLogsDescription": "Eventos de conexión de sitios y túneles, incluyendo conexiones y desconexiones.",
"httpDestRequestLogsTitle": "HTTPS Request Logs",
"httpDestRequestLogsDescription": "Registros de peticiones HTTP para recursos proxyficados, incluyendo método, ruta y código de respuesta.",
"httpDestSaveChanges": "Guardar Cambios",
"httpDestCreateDestination": "Crear destino",
"httpDestUpdatedSuccess": "Destino actualizado correctamente",
"httpDestCreatedSuccess": "Destino creado correctamente",
"httpDestUpdateFailed": "Error al actualizar destino",
"httpDestCreateFailed": "Error al crear el destino",
"idpAddActionCreateNew": "Crear nuevo proveedor de identidad",
"idpAddActionImportFromOrg": "Importar de otra organización",
"idpImportDialogTitle": "Importar Proveedor de Identidad",
"idpImportDialogDescription": "Elija un proveedor de identidad de una organización donde usted sea administrador. Se vinculará a esta organización.",
"idpImportSearchPlaceholder": "Buscar por nombre de organización o proveedor...",
"idpImportEmpty": "No se encontraron proveedores de identidad.",
"idpImportedDescription": "Proveedor de identidad importado con éxito.",
"idpDeleteGlobalQuestion": "¿Está seguro de que desea eliminar permanentemente este proveedor de identidad?",
"idpDeleteGlobalDescription": "Esto eliminará permanentemente el proveedor de identidad de todas las organizaciones con las que está asociado.",
"idpUnassociateTitle": "Desasociar Proveedor de Identidad",
"idpUnassociateQuestion": "¿Está seguro de que desea desasociar este proveedor de identidad de esta organización?",
"idpUnassociateDescription": "Todos los usuarios asociados con este proveedor de identidad serán eliminados de esta organización, pero el proveedor de identidad continuará existiendo para otras organizaciones asociadas.",
"idpUnassociateConfirm": "Confirme Desasociar Proveedor de Identidad",
"idpUnassociateWarning": "Esto no se puede deshacer para esta organización.",
"idpUnassociatedDescription": "Proveedor de identidad desasociado de esta organización con éxito",
"idpUnassociateMenu": "Desasociar",
"idpDeleteAllOrgsMenu": "Eliminar",
"publicIpEndpoint": "Endpoint"
}
+564 -38
View File
File diff suppressed because it is too large Load Diff
+603 -77
View File
File diff suppressed because it is too large Load Diff
+564 -38
View File
File diff suppressed because it is too large Load Diff
+564 -38
View File
File diff suppressed because it is too large Load Diff
+565 -39
View File
File diff suppressed because it is too large Load Diff
+564 -38
View File
File diff suppressed because it is too large Load Diff
+565 -39
View File
File diff suppressed because it is too large Load Diff
+564 -38
View File
File diff suppressed because it is too large Load Diff
+563 -37
View File
File diff suppressed because it is too large Load Diff
+564 -38
View File
File diff suppressed because it is too large Load Diff
+325 -23
View File
@@ -1,5 +1,7 @@
{
"setupCreate": "創建您的第一個組織、網站和資源",
"headerAuthCompatibilityInfo": "啟用此選項以在缺少驗證令牌時強制回傳 401 未授權回應。這對於不會在沒有伺服器挑戰的情況下發送憑證的瀏覽器或特定 HTTP 函式庫是必需的。",
"headerAuthCompatibility": "擴展相容性",
"setupNewOrg": "新建組織",
"setupCreateOrg": "創建組織",
"setupCreateResources": "創建資源",
@@ -13,7 +15,7 @@
"welcome": "歡迎使用 Pangolin",
"welcomeTo": "歡迎來到",
"componentsCreateOrg": "創建組織",
"componentsMember": "您屬於{count, plural, =0 {沒有組織} one {一個組織} other {# 個組織}}。",
"componentsMember": "您屬於 {count, plural, =0 {沒有組織} one {一個組織} other {# 個組織}}。",
"componentsInvalidKey": "檢測到無效或過期的許可證金鑰。按照許可證條款操作以繼續使用所有功能。",
"dismiss": "忽略",
"componentsLicenseViolation": "許可證超限:該伺服器使用了 {usedSites} 個站點,已超過授權的 {maxSites} 個。請遵守許可證條款以繼續使用全部功能。",
@@ -51,6 +53,9 @@
"siteQuestionRemove": "您確定要從組織中刪除該站點嗎?",
"siteManageSites": "管理站點",
"siteDescription": "允許通過安全隧道連接到您的網路",
"sitesBannerTitle": "連接任何網路",
"sitesBannerDescription": "站點是與遠端網路的連接,使 Pangolin 能夠為任何地方的使用者提供對公共或私有資源的存取。在任何可以執行二進位檔案或容器的地方安裝站點網路連接器 (Newt) 以建立連接。",
"sitesBannerButtonText": "安裝站點",
"siteCreate": "創建站點",
"siteCreateDescription2": "按照下面的步驟創建和連接一個新站點",
"siteCreateDescription": "創建一個新站點開始連接您的資源",
@@ -65,8 +70,8 @@
"siteLoadWGConfig": "正在載入 WireGuard 配置...",
"siteDocker": "擴展 Docker 部署詳細資訊",
"toggle": "切換",
"dockerCompose": "Docker 配置",
"dockerRun": "停靠欄",
"dockerCompose": "Docker Compose",
"dockerRun": "Docker Run",
"siteLearnLocal": "本地站點不需要隧道連接,點擊了解更多",
"siteConfirmCopy": "我已經複製了配置資訊",
"searchSitesProgress": "搜索站點...",
@@ -93,14 +98,15 @@
"siteNewtTunnelDescription": "最簡單的方式來連接到您的網路。不需要任何額外設置。",
"siteWg": "基本 WireGuard",
"siteWgDescription": "使用任何 WireGuard 用戶端來建立隧道。需要手動配置 NAT。",
"siteWgDescriptionSaas": "使用任何WireGuard用戶端建立隧道。需要手動配置NAT。僅適用於自託管節點。",
"siteWgDescriptionSaas": "使用任何 WireGuard 用戶端建立隧道。需要手動配置 NAT。僅適用於自託管節點。",
"siteLocalDescription": "僅限本地資源。不需要隧道。",
"siteLocalDescriptionSaas": "僅本地資源。沒有隧道。僅在遠程節點上可用。",
"siteSeeAll": "查看所有站點",
"siteTunnelDescription": "確定如何連接到您的網站",
"siteNewtCredentials": "Newt 憑",
"siteNewtCredentialsDescription": "這是 Newt 伺服器的身份驗證憑",
"siteCredentialsSave": "保存您的憑據",
"siteNewtCredentials": "Newt 憑",
"siteNewtCredentialsDescription": "這是 Newt 伺服器的身份驗證憑",
"remoteNodeCredentialsDescription": "這是遠端節點與伺服器進行驗證的方式",
"siteCredentialsSave": "保存您的憑證",
"siteCredentialsSaveDescription": "您只能看到一次。請確保將其複製並保存到一個安全的地方。",
"siteInfo": "站點資訊",
"status": "狀態",
@@ -144,8 +150,14 @@
"expires": "過期時間",
"never": "永不過期",
"shareErrorSelectResource": "請選擇一個資源",
"resourceTitle": "管理資源",
"resourceDescription": "為您的私人應用程式創建安全代理",
"proxyResourceTitle": "管理公開資源",
"proxyResourceDescription": "建立和管理可透過網頁瀏覽器公開存取的資源",
"proxyResourcesBannerTitle": "基於網頁的公開存取",
"proxyResourcesBannerDescription": "公開資源是任何人都可以透過網頁瀏覽器存取的 HTTPS 或 TCP/UDP 代理。與私有資源不同,它們不需要客戶端軟體,並且可以包含基於身份和情境感知的存取策略。",
"clientResourceTitle": "管理私有資源",
"clientResourceDescription": "建立和管理只能透過已連接的客戶端存取的資源",
"privateResourcesBannerTitle": "零信任私有存取",
"privateResourcesBannerDescription": "私有資源使用零信任安全性,確保使用者和機器只能存取您明確授權的資源。連接使用者裝置或機器客戶端以透過安全的虛擬私人網路存取這些資源。",
"resourcesSearch": "搜索資源...",
"resourceAdd": "添加資源",
"resourceErrorDelte": "刪除資源時出錯",
@@ -179,7 +191,7 @@
"baseDomain": "根域名",
"subdomnainDescription": "您的資源可以訪問的子域名。",
"resourceRawSettings": "TCP/UDP 設置",
"resourceRawSettingsDescription": "配置如何過 TCP/UDP 訪問您的資源。 您映射資源到主機Pangolin伺服器上的埠,這樣您就可以訪問伺服器-公共-ip:mapped埠的資源",
"resourceRawSettingsDescription": "設定如何過 TCP/UDP 存取資源",
"protocol": "協議",
"protocolSelect": "選擇協議",
"resourcePortNumber": "埠號",
@@ -320,6 +332,10 @@
"licenseErrorKeyActivate": "啟用許可證金鑰失敗",
"licenseErrorKeyActivateDescription": "啟用許可證金鑰時出錯。",
"licenseAbout": "關於許可協議",
"licenseBannerTitle": "Enable Your Enterprise License",
"licenseBannerDescription": "Unlock enterprise features for your self-hosted Pangolin instance. Purchase a license key to activate premium capabilities, then add it below.",
"licenseBannerGetLicense": "Get a License",
"licenseBannerViewDocs": "View Documentation",
"communityEdition": "社區版",
"licenseAboutDescription": "這是針對商業環境中使用Pangolin的商業和企業用戶。 如果您正在使用 Pangolin 供個人使用,您可以忽略此部分。",
"licenseKeyActivated": "授權金鑰已啟用",
@@ -436,6 +452,16 @@
"inviteEmailSent": "發送邀請郵件給用戶",
"inviteValid": "有效",
"selectDuration": "選擇持續時間",
"selectResource": "選擇資源",
"filterByResource": "依資源篩選",
"resetFilters": "重設篩選條件",
"totalBlocked": "被 Pangolin 阻擋的請求",
"totalRequests": "總請求數",
"requestsByCountry": "依國家/地區的請求",
"requestsByDay": "依日期的請求",
"blocked": "已阻擋",
"allowed": "已允許",
"topCountries": "熱門國家/地區",
"accessRoleSelect": "選擇角色",
"inviteEmailSentDescription": "一封電子郵件已經發送給用戶,帶有下面的訪問連結。他們必須訪問該連結才能接受邀請。",
"inviteSentDescription": "用戶已被邀請。他們必須訪問下面的連結才能接受邀請。",
@@ -465,7 +491,7 @@
"proxyErrorTls": "無效的 TLS 伺服器名稱。使用域名格式,或保存空以刪除 TLS 伺服器名稱。",
"proxyEnableSSL": "啟用 SSL",
"proxyEnableSSLDescription": "啟用 SSL/TLS 加密以確保您目標的 HTTPS 連接。",
"target": "Target",
"target": "目標",
"configureTarget": "配置目標",
"targetErrorFetch": "獲取目標失敗",
"targetErrorFetchDescription": "獲取目標時出錯",
@@ -516,6 +542,8 @@
"targetCreatedDescription": "目標已成功創建",
"targetErrorCreate": "創建目標失敗",
"targetErrorCreateDescription": "創建目標時出錯",
"tlsServerName": "TLS 伺服器名稱",
"tlsServerNameDescription": "用於 SNI 的 TLS 伺服器名稱",
"save": "保存",
"proxyAdditional": "附加代理設置",
"proxyAdditionalDescription": "配置你的資源如何處理代理設置",
@@ -702,6 +730,7 @@
"resourceTransferSubmit": "轉移資源",
"siteDestination": "目標站點",
"searchSites": "搜索站點",
"countries": "國家/地區",
"accessRoleCreate": "創建角色",
"accessRoleCreateDescription": "創建一個新角色來分組用戶並管理他們的權限。",
"accessRoleCreateSubmit": "創建角色",
@@ -825,6 +854,7 @@
"orgPolicyConfig": "配置組織訪問權限",
"idpUpdatedDescription": "身份提供商更新成功",
"redirectUrl": "重定向網址",
"orgIdpRedirectUrls": "重新導向網址",
"redirectUrlAbout": "關於重定向網址",
"redirectUrlAboutDescription": "這是用戶在驗證後將被重定向到的URL。您需要在身份提供商設置中配置此URL。",
"pangolinAuth": "認證 - Pangolin",
@@ -909,6 +939,10 @@
"passwordResetSent": "我們將發送一個驗證碼到這個電子郵件地址。",
"passwordResetCode": "驗證碼",
"passwordResetCodeDescription": "請檢查您的電子郵件以獲取驗證碼。",
"generatePasswordResetCode": "產生密碼重設代碼",
"passwordResetCodeGenerated": "密碼重設代碼已產生",
"passwordResetCodeGeneratedDescription": "請將此代碼分享給使用者。他們可以用它來重設密碼。",
"passwordResetUrl": "重設網址",
"passwordNew": "新密碼",
"passwordNewConfirm": "確認新密碼",
"changePassword": "更改密碼",
@@ -926,6 +960,9 @@
"pincodeAuth": "驗證器代碼",
"pincodeSubmit2": "提交代碼",
"passwordResetSubmit": "請求重設",
"passwordResetAlreadyHaveCode": "輸入代碼",
"passwordResetSmtpRequired": "請聯絡您的管理員",
"passwordResetSmtpRequiredDescription": "需要密碼重設代碼才能重設您的密碼。請聯絡您的管理員尋求協助。",
"passwordBack": "回到密碼",
"loginBack": "返回登錄",
"signup": "註冊",
@@ -1013,6 +1050,7 @@
"updateOrgUser": "更新組織用戶",
"createOrgUser": "創建組織用戶",
"actionUpdateOrg": "更新組織",
"actionRemoveInvitation": "移除邀請",
"actionUpdateUser": "更新用戶",
"actionGetUser": "獲取用戶",
"actionGetOrgUser": "獲取組織用戶",
@@ -1057,6 +1095,7 @@
"actionRemoveUser": "刪除用戶",
"actionListUsers": "列出用戶",
"actionAddUserRole": "添加用戶角色",
"actionSetUserOrgRoles": "Set User Roles",
"actionGenerateAccessToken": "生成訪問令牌",
"actionDeleteAccessToken": "刪除訪問令牌",
"actionListAccessTokens": "訪問令牌",
@@ -1093,12 +1132,15 @@
"actionListSiteResources": "列出站點資源",
"actionUpdateSiteResource": "更新站點資源",
"actionListInvitations": "邀請列表",
"actionExportLogs": "匯出日誌",
"actionViewLogs": "查看日誌",
"noneSelected": "未選擇",
"orgNotFound2": "未找到組織。",
"searchProgress": "搜索中...",
"create": "創建",
"orgs": "組織",
"loginError": "登錄時出錯",
"loginRequiredForDevice": "需要登入以驗證您的裝置。",
"passwordForgot": "忘記密碼?",
"otpAuth": "兩步驗證",
"otpAuthDescription": "從您的身份驗證程序中輸入代碼或您的單次備份代碼。",
@@ -1153,8 +1195,12 @@
"sidebarHome": "首頁",
"sidebarSites": "站點",
"sidebarResources": "資源",
"sidebarProxyResources": "公開",
"sidebarClientResources": "私有",
"sidebarAccessControl": "訪問控制",
"sidebarLogsAndAnalytics": "日誌與分析",
"sidebarUsers": "用戶",
"sidebarAdmin": "管理員",
"sidebarInvitations": "邀請",
"sidebarRoles": "角色",
"sidebarShareableLinks": "分享連結",
@@ -1164,8 +1210,14 @@
"sidebarIdentityProviders": "身份提供商",
"sidebarLicense": "證書",
"sidebarClients": "用戶端",
"sidebarUserDevices": "使用者",
"sidebarMachineClients": "機器",
"sidebarDomains": "域",
"sidebarGeneral": "管理",
"sidebarLogAndAnalytics": "日誌與分析",
"sidebarBluePrints": "藍圖",
"sidebarOrganization": "組織",
"sidebarLogsAnalytics": "分析",
"blueprints": "藍圖",
"blueprintsDescription": "應用聲明配置並查看先前運行的",
"blueprintAdd": "添加藍圖",
@@ -1275,12 +1327,24 @@
"accountSetupSuccess": "帳號設定完成!歡迎來到 Pangolin",
"documentation": "文件",
"saveAllSettings": "保存所有設置",
"saveResourceTargets": "儲存目標",
"saveResourceHttp": "儲存代理設定",
"saveProxyProtocol": "儲存代理協定設定",
"settingsUpdated": "設置已更新",
"settingsUpdatedDescription": "所有設置已成功更新",
"settingsErrorUpdate": "設置更新失敗",
"settingsErrorUpdateDescription": "更新設置時發生錯誤",
"sidebarCollapse": "摺疊",
"sidebarExpand": "展開",
"productUpdateMoreInfo": "還有 {noOfUpdates} 項更新",
"productUpdateInfo": "{noOfUpdates} 項更新",
"productUpdateWhatsNew": "新功能",
"productUpdateTitle": "產品更新",
"productUpdateEmpty": "沒有更新",
"dismissAll": "全部關閉",
"pangolinUpdateAvailable": "有可用更新",
"pangolinUpdateAvailableInfo": "版本 {version} 已準備好安裝",
"pangolinUpdateAvailableReleaseNotes": "查看發行說明",
"newtUpdateAvailable": "更新可用",
"newtUpdateAvailableInfo": "新版本的 Newt 已可用。請更新到最新版本以獲得最佳體驗。",
"domainPickerEnterDomain": "域名",
@@ -1423,6 +1487,9 @@
"and": "和",
"privacyPolicy": "隱私政策"
},
"signUpMarketing": {
"keepMeInTheLoop": "透過電子郵件接收新聞、更新和新功能通知。"
},
"siteRequired": "需要站點。",
"olmTunnel": "Olm 隧道",
"olmTunnelDescription": "使用 Olm 進行用戶端連接",
@@ -1456,15 +1523,14 @@
"sitesFetchError": "獲取站點時出錯。",
"olmErrorFetchReleases": "獲取 Olm 發布版本時出錯。",
"olmErrorFetchLatest": "獲取最新 Olm 發布版本時出錯。",
"remoteSubnets": "遠程子網",
"enterCidrRange": "輸入 CIDR 範圍",
"remoteSubnetsDescription": "添加可以通過用戶端遠端存取該站點的 CIDR 範圍。使用類似 10.0.0.0/24 的格式。這僅適用於 VPN 用戶端連接。",
"resourceEnableProxy": "啟用公共代理",
"resourceEnableProxyDescription": "啟用到此資源的公共代理。這允許外部網路通過開放埠訪問資源。需要 Traefik 配置。",
"externalProxyEnabled": "外部代理已啟用",
"addNewTarget": "添加新目標",
"targetsList": "目標列表",
"advancedMode": "高級模式",
"advancedSettings": "進階設定",
"targetErrorDuplicateTargetFound": "找到重複的目標",
"healthCheckHealthy": "正常",
"healthCheckUnhealthy": "不正常",
@@ -1476,6 +1542,7 @@
"enableHealthChecksDescription": "監視此目標的健康狀況。如果需要,您可以監視一個不同的終點。",
"healthScheme": "方法",
"healthSelectScheme": "選擇方法",
"healthCheckPortInvalid": "健康檢查連接埠必須介於 1 到 65535 之間",
"healthCheckPath": "路徑",
"healthHostname": "IP / 主機",
"healthPort": "埠",
@@ -1524,9 +1591,15 @@
"resourcesTableNoProxyResourcesFound": "未找到代理資源。",
"resourcesTableNoInternalResourcesFound": "未找到內部資源。",
"resourcesTableDestination": "目標",
"resourcesTableTheseResourcesForUseWith": "這些資源供...使用",
"resourcesTableAlias": "別名",
"resourcesTableClients": "用戶端",
"resourcesTableAndOnlyAccessibleInternally": "且僅在與用戶端連接時可內部訪問。",
"resourcesTableNoTargets": "無目標",
"resourcesTableHealthy": "健康",
"resourcesTableDegraded": "降級",
"resourcesTableOffline": "離線",
"resourcesTableUnknown": "未知",
"resourcesTableNotMonitored": "未監控",
"editInternalResourceDialogEditClientResource": "編輯用戶端資源",
"editInternalResourceDialogUpdateResourceProperties": "更新 {resourceName} 的資源屬性和目標配置。",
"editInternalResourceDialogResourceProperties": "資源屬性",
@@ -1547,6 +1620,17 @@
"editInternalResourceDialogInvalidIPAddressFormat": "無效的 IP 位址格式",
"editInternalResourceDialogDestinationPortMin": "目標埠必須至少為 1",
"editInternalResourceDialogDestinationPortMax": "目標埠必須小於 65536",
"editInternalResourceDialogPortModeRequired": "連接埠模式需要協定、代理連接埠和目標連接埠",
"editInternalResourceDialogMode": "模式",
"editInternalResourceDialogModePort": "連接埠",
"editInternalResourceDialogModeHost": "主機",
"editInternalResourceDialogModeCidr": "CIDR",
"editInternalResourceDialogDestination": "目的地",
"editInternalResourceDialogDestinationHostDescription": "站點網路上資源的 IP 位址或主機名稱。",
"editInternalResourceDialogDestinationIPDescription": "站點網路上資源的 IP 或主機名稱位址。",
"editInternalResourceDialogDestinationCidrDescription": "站點網路上資源的 CIDR 範圍。",
"editInternalResourceDialogAlias": "別名",
"editInternalResourceDialogAliasDescription": "此資源的可選內部 DNS 別名。",
"createInternalResourceDialogNoSitesAvailable": "暫無可用站點",
"createInternalResourceDialogNoSitesAvailableDescription": "您需要至少配置一個子網的 Newt 站點來創建內部資源。",
"createInternalResourceDialogClose": "關閉",
@@ -1555,9 +1639,8 @@
"createInternalResourceDialogResourceProperties": "資源屬性",
"createInternalResourceDialogName": "名稱",
"createInternalResourceDialogSite": "站點",
"createInternalResourceDialogSelectSite": "選擇站點...",
"createInternalResourceDialogSearchSites": "搜索站點...",
"createInternalResourceDialogNoSitesFound": "未找到站點。",
"selectSite": "選擇站點...",
"noSitesFound": "找不到站點。",
"createInternalResourceDialogProtocol": "協議",
"createInternalResourceDialogTcp": "TCP",
"createInternalResourceDialogUdp": "UDP",
@@ -1580,11 +1663,22 @@
"createInternalResourceDialogInvalidIPAddressFormat": "無效的 IP 位址格式",
"createInternalResourceDialogDestinationPortMin": "目標埠必須至少為 1",
"createInternalResourceDialogDestinationPortMax": "目標埠必須小於 65536",
"createInternalResourceDialogPortModeRequired": "連接埠模式需要協定、代理連接埠和目標連接埠",
"createInternalResourceDialogMode": "模式",
"createInternalResourceDialogModePort": "連接埠",
"createInternalResourceDialogModeHost": "主機",
"createInternalResourceDialogModeCidr": "CIDR",
"createInternalResourceDialogDestination": "目的地",
"createInternalResourceDialogDestinationHostDescription": "站點網路上資源的 IP 位址或主機名稱。",
"createInternalResourceDialogDestinationCidrDescription": "站點網路上資源的 CIDR 範圍。",
"createInternalResourceDialogAlias": "別名",
"createInternalResourceDialogAliasDescription": "此資源的可選內部 DNS 別名。",
"siteConfiguration": "配置",
"siteAcceptClientConnections": "接受用戶端連接",
"siteAcceptClientConnectionsDescription": "允許其他設備透過此 Newt 實例使用用戶端作為閘道器連接。",
"siteAddress": "站點地址",
"siteAddressDescription": "指定主機的 IP 位址以供用戶端連接。這是 Pangolin 網路中站點的內部地址,供用戶端訪問。必須在 Org 子網內。",
"siteNameDescription": "站點的顯示名稱,可以稍後更改。",
"autoLoginExternalIdp": "自動使用外部 IDP 登錄",
"autoLoginExternalIdpDescription": "立即將用戶重定向到外部 IDP 進行身份驗證。",
"selectIdp": "選擇 IDP",
@@ -1608,6 +1702,8 @@
"remoteExitNodeConfirmDelete": "確認刪除節點",
"remoteExitNodeDelete": "刪除節點",
"sidebarRemoteExitNodes": "遠程節點",
"remoteExitNodeId": "ID",
"remoteExitNodeSecretKey": "密鑰",
"remoteExitNodeCreate": {
"title": "創建節點",
"description": "創建一個新節點來擴展您的網路連接",
@@ -1731,12 +1827,33 @@
"idpAzureClientIdDescription2": "您的 Azure 應用程式註冊用戶端 ID",
"idpAzureClientSecretDescription2": "您的 Azure 應用程式註冊用戶端金鑰",
"idpGoogleDescription": "Google OAuth2/OIDC 提供商",
"idpAzureDescription": "Microsoft Azure OAuth2/OIDC provider",
"idpAzureDescription": "Microsoft Azure OAuth2/OIDC 提供者",
"subnet": "子網",
"subnetDescription": "此組織網路配置的子網。",
"customDomain": "自訂網域",
"authPage": "認證頁面",
"authPageDescription": "配置您的組織認證頁面",
"authPageDomain": "認證頁面域",
"authPageBranding": "自訂品牌",
"authPageBrandingDescription": "設定此組織驗證頁面上顯示的品牌",
"authPageBrandingUpdated": "驗證頁面品牌更新成功",
"authPageBrandingRemoved": "驗證頁面品牌移除成功",
"authPageBrandingRemoveTitle": "移除驗證頁面品牌",
"authPageBrandingQuestionRemove": "您確定要移除驗證頁面的品牌嗎?",
"authPageBrandingDeleteConfirm": "確認刪除品牌",
"brandingLogoURL": "Logo 網址",
"brandingPrimaryColor": "主要顏色",
"brandingLogoWidth": "寬度 (px)",
"brandingLogoHeight": "高度 (px)",
"brandingOrgTitle": "組織驗證頁面標題",
"brandingOrgDescription": "{orgName} 將被替換為組織名稱",
"brandingOrgSubtitle": "組織驗證頁面副標題",
"brandingResourceTitle": "資源驗證頁面標題",
"brandingResourceSubtitle": "資源驗證頁面副標題",
"brandingResourceDescription": "{resourceName} 將被替換為組織名稱",
"saveAuthPageDomain": "儲存網域",
"saveAuthPageBranding": "儲存品牌",
"removeAuthPageBranding": "移除品牌",
"noDomainSet": "沒有域設置",
"changeDomain": "更改域",
"selectDomain": "選擇域",
@@ -1764,6 +1881,15 @@
"orgAuthChooseIdpDescription": "選擇您的身份提供商以繼續",
"orgAuthNoIdpConfigured": "此機構沒有配置任何身份提供者。您可以使用您的 Pangolin 身份登錄。",
"orgAuthSignInWithPangolin": "使用 Pangolin 登錄",
"orgAuthSignInToOrg": "登入組織",
"orgAuthSelectOrgTitle": "組織登入",
"orgAuthSelectOrgDescription": "輸入您的組織 ID 以繼續",
"orgAuthOrgIdPlaceholder": "your-organization",
"orgAuthOrgIdHelp": "輸入您組織的唯一識別碼",
"orgAuthSelectOrgHelp": "輸入組織 ID 後,您將被導向到組織的登入頁面,在那裡您可以使用 SSO 或組織憑證。",
"orgAuthRememberOrgId": "記住此組織 ID",
"orgAuthBackToSignIn": "返回標準登入",
"orgAuthNoAccount": "沒有帳戶?",
"subscriptionRequiredToUse": "需要訂閱才能使用此功能。",
"idpDisabled": "身份提供者已禁用。",
"orgAuthPageDisabled": "組織認證頁面已禁用。",
@@ -1778,6 +1904,8 @@
"enableTwoFactorAuthentication": "啟用兩步驗證",
"completeSecuritySteps": "完成安全步驟",
"securitySettings": "安全設定",
"dangerSection": "危險區域",
"dangerSectionDescription": "永久刪除與此組織相關的所有資料",
"securitySettingsDescription": "配置您組織的安全策略",
"requireTwoFactorForAllUsers": "所有用戶需要兩步驗證",
"requireTwoFactorDescription": "如果啟用,此組織的所有內部用戶必須啟用雙重身份驗證才能訪問組織。",
@@ -1815,7 +1943,7 @@
"securityPolicyChangeWarningText": "這將影響組織中的所有用戶",
"authPageErrorUpdateMessage": "更新身份驗證頁面設置時出錯",
"authPageErrorUpdate": "無法更新認證頁面",
"authPageUpdated": "身份驗證頁面更新成功",
"authPageDomainUpdated": "驗證頁面網域更新成功",
"healthCheckNotAvailable": "本地的",
"rewritePath": "重寫路徑",
"rewritePathDescription": "在轉發到目標之前,可以選擇重寫路徑。",
@@ -1841,8 +1969,19 @@
"enterpriseEdition": "企業版",
"unlicensed": "未授權",
"beta": "測試版",
"manageClients": "管理用戶端",
"manageClientsDescription": "用戶端是可以連接到您的站點的設備",
"manageUserDevices": "使用者裝置",
"manageUserDevicesDescription": "查看和管理使用者用於私密連接資源的裝置",
"downloadClientBannerTitle": "下載 Pangolin 客戶端",
"downloadClientBannerDescription": "下載適用於您系統的 Pangolin 客戶端,以連接到 Pangolin 網路並私密存取資源。",
"manageMachineClients": "管理機器客戶端",
"manageMachineClientsDescription": "建立和管理伺服器和系統用於私密連接資源的客戶端",
"machineClientsBannerTitle": "伺服器與自動化系統",
"machineClientsBannerDescription": "機器客戶端適用於與特定使用者無關的伺服器和自動化系統。它們使用 ID 和密鑰進行驗證,可以透過 Pangolin CLI、Olm CLI 或 Olm 容器執行。",
"machineClientsBannerPangolinCLI": "Pangolin CLI",
"machineClientsBannerOlmCLI": "Olm CLI",
"machineClientsBannerOlmContainer": "Olm 容器",
"clientsTableUserClients": "使用者",
"clientsTableMachineClients": "機器",
"licenseTableValidUntil": "有效期至",
"saasLicenseKeysSettingsTitle": "企業許可證",
"saasLicenseKeysSettingsDescription": "為自我託管的 Pangolin 實例生成和管理企業許可證金鑰",
@@ -1982,6 +2121,7 @@
"clientMessageRemove": "一旦刪除,用戶端將無法連接到站點。",
"sidebarLogs": "日誌",
"request": "請求",
"requests": "請求",
"logs": "日誌",
"logsSettingsDescription": "監視從此 orginization 中收集的日誌",
"searchLogs": "搜索日誌...",
@@ -1990,6 +2130,8 @@
"timestamp": "時間戳",
"accessLogs": "訪問日誌",
"exportCsv": "導出 CSV",
"exportError": "匯出 CSV 時發生未知錯誤",
"exportCsvTooltip": "在時間範圍內",
"actorId": "執行者 ID",
"allowedByRule": "根據規則允許",
"allowedNoAuth": "無認證",
@@ -2007,6 +2149,7 @@
"ip": "IP",
"reason": "原因",
"requestLogs": "請求日誌",
"requestAnalytics": "請求分析",
"host": "主機",
"location": "地點",
"actionLogs": "操作日誌",
@@ -2016,6 +2159,7 @@
"logRetention": "日誌保留",
"logRetentionDescription": "管理不同類型的日誌為這個機構保留多長時間或禁用這些日誌",
"requestLogsDescription": "查看此機構資源的詳細請求日誌",
"requestAnalyticsDescription": "查看此組織資源的詳細請求分析",
"logRetentionRequestLabel": "請求日誌保留",
"logRetentionRequestDescription": "保留請求日誌的時間",
"logRetentionAccessLabel": "訪問日誌保留",
@@ -2029,6 +2173,7 @@
"logRetention30Days": "30 天",
"logRetention90Days": "90 天",
"logRetentionForever": "永遠的",
"logRetentionEndOfFollowingYear": "次年年底",
"actionLogsDescription": "查看此機構執行的操作歷史",
"accessLogsDescription": "查看此機構資源的訪問認證請求",
"licenseRequiredToUse": "需要企業許可證才能使用此功能。",
@@ -2084,6 +2229,43 @@
"supportMessageSent": "消息已發送!",
"supportWillContact": "我們很快就會聯繫起來!",
"selectLogRetention": "選擇保留日誌",
"terms": "條款",
"privacy": "隱私權",
"security": "安全性",
"docs": "文件",
"deviceActivation": "裝置啟用",
"deviceCodeInvalidFormat": "代碼必須為 9 個字元(例如:A1AJ-N5JD",
"deviceCodeInvalidOrExpired": "代碼無效或已過期",
"deviceCodeVerifyFailed": "驗證裝置代碼失敗",
"signedInAs": "已登入為",
"deviceCodeEnterPrompt": "輸入裝置上顯示的代碼",
"continue": "繼續",
"deviceUnknownLocation": "未知位置",
"deviceAuthorizationRequested": "此授權請求來自 {location},時間為 {date}。請確保您信任此裝置,因為它將獲得帳戶存取權限。",
"deviceLabel": "裝置:{deviceName}",
"deviceWantsAccess": "想要存取您的帳戶",
"deviceExistingAccess": "現有存取權限:",
"deviceFullAccess": "完整帳戶存取權限",
"deviceOrganizationsAccess": "存取您帳戶有權限的所有組織",
"deviceAuthorize": "授權 {applicationName}",
"deviceConnected": "裝置已連接!",
"deviceAuthorizedMessage": "裝置已獲授權存取您的帳戶。請返回客戶端應用程式。",
"pangolinCloud": "Pangolin 雲端",
"viewDevices": "查看裝置",
"viewDevicesDescription": "管理您已連接的裝置",
"noDevices": "找不到裝置",
"dateCreated": "建立日期",
"unnamedDevice": "未命名裝置",
"deviceQuestionRemove": "您確定要刪除此裝置嗎?",
"deviceMessageRemove": "此操作無法復原。",
"deviceDeleteConfirm": "刪除裝置",
"deleteDevice": "刪除裝置",
"errorLoadingDevices": "載入裝置時發生錯誤",
"failedToLoadDevices": "載入裝置失敗",
"deviceDeleted": "裝置已刪除",
"deviceDeletedDescription": "裝置已成功刪除。",
"errorDeletingDevice": "刪除裝置時發生錯誤",
"failedToDeleteDevice": "刪除裝置失敗",
"showColumns": "顯示列",
"hideColumns": "隱藏列",
"columnVisibility": "列可見性",
@@ -2097,5 +2279,125 @@
"selectedResources": "選定的資源",
"enableSelected": "啟用選中的",
"disableSelected": "禁用選中的",
"checkSelectedStatus": "檢查選中的狀態"
}
"checkSelectedStatus": "檢查選中的狀態",
"clients": "客戶端",
"accessClientSelect": "選擇機器客戶端",
"resourceClientDescription": "可以存取此資源的機器客戶端",
"regenerate": "重新產生",
"credentials": "憑證",
"savecredentials": "儲存憑證",
"regenerateCredentialsButton": "重新產生憑證",
"regenerateCredentials": "重新產生憑證",
"generatedcredentials": "已產生的憑證",
"copyandsavethesecredentials": "複製並儲存這些憑證",
"copyandsavethesecredentialsdescription": "離開此頁面後將不會再顯示這些憑證。請立即安全儲存。",
"credentialsSaved": "憑證已儲存",
"credentialsSavedDescription": "憑證已成功重新產生並儲存。",
"credentialsSaveError": "憑證儲存錯誤",
"credentialsSaveErrorDescription": "重新產生和儲存憑證時發生錯誤。",
"regenerateCredentialsWarning": "重新產生憑證將使先前的憑證失效並導致斷線。請確保更新任何使用這些憑證的設定。",
"confirm": "確認",
"regenerateCredentialsConfirmation": "您確定要重新產生憑證嗎?",
"endpoint": "端點",
"Id": "ID",
"SecretKey": "密鑰",
"niceId": "友善 ID",
"niceIdUpdated": "友善 ID 已更新",
"niceIdUpdatedSuccessfully": "友善 ID 更新成功",
"niceIdUpdateError": "更新友善 ID 時發生錯誤",
"niceIdUpdateErrorDescription": "更新友善 ID 時發生錯誤。",
"niceIdCannotBeEmpty": "友善 ID 不能為空",
"enterIdentifier": "輸入識別碼",
"identifier": "識別碼",
"deviceLoginUseDifferentAccount": "不是您嗎?使用其他帳戶。",
"deviceLoginDeviceRequestingAccessToAccount": "有裝置正在請求存取此帳戶。",
"noData": "無資料",
"machineClients": "機器客戶端",
"install": "安裝",
"run": "執行",
"clientNameDescription": "客戶端的顯示名稱,可以稍後更改。",
"clientAddress": "客戶端位址(進階)",
"setupFailedToFetchSubnet": "取得預設子網路失敗",
"setupSubnetAdvanced": "子網路(進階)",
"setupSubnetDescription": "此組織內部網路的子網路。",
"setupUtilitySubnet": "工具子網路(進階)",
"setupUtilitySubnetDescription": "此組織別名位址和 DNS 伺服器的子網路。",
"siteRegenerateAndDisconnect": "重新產生並斷開連接",
"siteRegenerateAndDisconnectConfirmation": "您確定要重新產生憑證並斷開此站點的連接嗎?",
"siteRegenerateAndDisconnectWarning": "這將重新產生憑證並立即斷開站點連接。站點需要使用新憑證重新啟動。",
"siteRegenerateCredentialsConfirmation": "您確定要重新產生此站點的憑證嗎?",
"siteRegenerateCredentialsWarning": "這將重新產生憑證。站點將保持連接,直到您手動重新啟動並使用新憑證。",
"clientRegenerateAndDisconnect": "重新產生並斷開連接",
"clientRegenerateAndDisconnectConfirmation": "您確定要重新產生憑證並斷開此客戶端的連接嗎?",
"clientRegenerateAndDisconnectWarning": "這將重新產生憑證並立即斷開客戶端連接。客戶端需要使用新憑證重新啟動。",
"clientRegenerateCredentialsConfirmation": "您確定要重新產生此客戶端的憑證嗎?",
"clientRegenerateCredentialsWarning": "這將重新產生憑證。客戶端將保持連接,直到您手動重新啟動並使用新憑證。",
"remoteExitNodeRegenerateAndDisconnect": "重新產生並斷開連接",
"remoteExitNodeRegenerateAndDisconnectConfirmation": "您確定要重新產生憑證並斷開此遠端出口節點的連接嗎?",
"remoteExitNodeRegenerateAndDisconnectWarning": "這將重新產生憑證並立即斷開遠端出口節點連接。遠端出口節點需要使用新憑證重新啟動。",
"remoteExitNodeRegenerateCredentialsConfirmation": "您確定要重新產生此遠端出口節點的憑證嗎?",
"remoteExitNodeRegenerateCredentialsWarning": "這將重新產生憑證。遠端出口節點將保持連接,直到您手動重新啟動並使用新憑證。",
"agent": "代理",
"personalUseOnly": "僅限個人使用",
"loginPageLicenseWatermark": "此實例僅授權個人使用。",
"instanceIsUnlicensed": "此實例未授權。",
"portRestrictions": "連接埠限制",
"allPorts": "全部",
"custom": "自訂",
"allPortsAllowed": "允許所有連接埠",
"allPortsBlocked": "阻擋所有連接埠",
"tcpPortsDescription": "指定此資源允許的 TCP 連接埠。使用「*」表示所有連接埠,留空表示阻擋全部,或輸入以逗號分隔的連接埠和範圍(例如:80,443,8000-9000)。",
"udpPortsDescription": "指定此資源允許的 UDP 連接埠。使用「*」表示所有連接埠,留空表示阻擋全部,或輸入以逗號分隔的連接埠和範圍(例如:53,123,500-600)。",
"organizationLoginPageTitle": "組織登入頁面",
"organizationLoginPageDescription": "自訂此組織的登入頁面",
"resourceLoginPageTitle": "資源登入頁面",
"resourceLoginPageDescription": "自訂個別資源的登入頁面",
"enterConfirmation": "輸入確認",
"blueprintViewDetails": "詳細資訊",
"defaultIdentityProvider": "預設身份提供者",
"defaultIdentityProviderDescription": "當選擇預設身份提供者時,使用者將自動被重新導向到該提供者進行驗證。",
"editInternalResourceDialogNetworkSettings": "網路設定",
"editInternalResourceDialogAccessPolicy": "存取策略",
"editInternalResourceDialogAddRoles": "新增角色",
"editInternalResourceDialogAddUsers": "新增使用者",
"editInternalResourceDialogAddClients": "新增客戶端",
"editInternalResourceDialogDestinationLabel": "目的地",
"editInternalResourceDialogDestinationDescription": "指定內部資源的目的地位址。根據所選模式,這可以是主機名稱、IP 位址或 CIDR 範圍。可選擇設定內部 DNS 別名以便識別。",
"editInternalResourceDialogPortRestrictionsDescription": "限制對特定 TCP/UDP 連接埠的存取,或允許/阻擋所有連接埠。",
"editInternalResourceDialogTcp": "TCP",
"editInternalResourceDialogUdp": "UDP",
"editInternalResourceDialogIcmp": "ICMP",
"editInternalResourceDialogAccessControl": "存取控制",
"editInternalResourceDialogAccessControlDescription": "控制哪些角色、使用者和機器客戶端在連接時可以存取此資源。管理員始終擁有存取權限。",
"editInternalResourceDialogPortRangeValidationError": "連接埠範圍必須是「*」表示所有連接埠,或以逗號分隔的連接埠和範圍列表(例如:「80,443,8000-9000」)。連接埠必須介於 1 到 65535 之間。",
"orgAuthWhatsThis": "我在哪裡可以找到我的組織 ID?",
"learnMore": "了解更多",
"backToHome": "返回首頁",
"needToSignInToOrg": "需要使用您組織的身份提供者嗎?",
"maintenanceMode": "維護模式",
"maintenanceModeDescription": "向訪客顯示維護頁面",
"maintenanceModeType": "維護模式類型",
"showMaintenancePage": "向訪客顯示維護頁面",
"enableMaintenanceMode": "啟用維護模式",
"automatic": "自動",
"automaticModeDescription": "僅在所有後端目標都關閉或不健康時顯示維護頁面。只要至少有一個目標健康,您的資源就會正常運作。",
"forced": "強制",
"forcedModeDescription": "無論後端健康狀況如何,始終顯示維護頁面。當您想要阻止所有存取時,用於計劃維護。",
"warning:": "警告:",
"forcedeModeWarning": "所有流量將被導向維護頁面。您的後端資源將不會收到任何請求。",
"pageTitle": "頁面標題",
"pageTitleDescription": "維護頁面上顯示的主標題",
"maintenancePageMessage": "維護訊息",
"maintenancePageMessagePlaceholder": "我們很快就會回來!我們的網站目前正在進行預定維護。",
"maintenancePageMessageDescription": "說明維護的詳細訊息",
"maintenancePageTimeTitle": "預計完成時間(可選)",
"maintenanceTime": "例如:2 小時、11 月 1 日下午 5:00",
"maintenanceEstimatedTimeDescription": "您預計何時完成維護",
"editDomain": "編輯網域",
"editDomainDescription": "為您的資源選擇網域",
"maintenanceModeDisabledTooltip": "此功能需要有效的授權才能啟用。",
"maintenanceScreenTitle": "服務暫時無法使用",
"maintenanceScreenMessage": "我們目前遇到技術問題。請稍後再試。",
"maintenanceScreenEstimatedCompletion": "預計完成時間:",
"createInternalResourceDialogDestinationRequired": "目的地為必填欄位"
}
+2693 -6756
View File
File diff suppressed because it is too large Load Diff
+70 -71
View File
@@ -3,7 +3,7 @@
"version": "0.0.0",
"private": true,
"type": "module",
"description": "Tunneled Reverse Proxy Management Server with Identity and Access Control and Dashboard UI",
"description": "Identity-aware VPN and proxy for remote access to anything, anywhere and Dashboard UI",
"homepage": "https://github.com/fosrl/pangolin",
"repository": {
"type": "git",
@@ -12,30 +12,29 @@
"license": "SEE LICENSE IN LICENSE AND README.md",
"scripts": {
"dev": "NODE_ENV=development ENVIRONMENT=dev tsx watch server/index.ts",
"db:pg:generate": "drizzle-kit generate --config=./drizzle.pg.config.ts",
"db:sqlite:generate": "drizzle-kit generate --config=./drizzle.sqlite.config.ts",
"db:pg:push": "npx tsx server/db/pg/migrate.ts",
"db:sqlite:push": "npx tsx server/db/sqlite/migrate.ts",
"db:sqlite:studio": "drizzle-kit studio --config=./drizzle.sqlite.config.ts",
"db:pg:studio": "drizzle-kit studio --config=./drizzle.pg.config.ts",
"dev:check": "npx tsc --noEmit && npm run format:check",
"dev:setup": "cp config/config.example.yml config/config.yml && npm run set:oss && npm run set:sqlite && npm run db:sqlite:generate && npm run db:sqlite:push",
"db:generate": "drizzle-kit generate --config=./drizzle.config.ts",
"db:push": "npx tsx server/db/migrate.ts",
"db:studio": "drizzle-kit studio --config=./drizzle.config.ts",
"db:clear-migrations": "rm -rf server/migrations",
"set:oss": "echo 'export const build = \"oss\" as \"saas\" | \"enterprise\" | \"oss\";' > server/build.ts && cp tsconfig.oss.json tsconfig.json",
"set:saas": "echo 'export const build = \"saas\" as \"saas\" | \"enterprise\" | \"oss\";' > server/build.ts && cp tsconfig.saas.json tsconfig.json",
"set:enterprise": "echo 'export const build = \"enterprise\" as \"saas\" | \"enterprise\" | \"oss\";' > server/build.ts && cp tsconfig.enterprise.json tsconfig.json",
"set:sqlite": "echo 'export * from \"./sqlite\";\nexport const driver: \"pg\" | \"sqlite\" = \"sqlite\";' > server/db/index.ts",
"set:pg": "echo 'export * from \"./pg\";\nexport const driver: \"pg\" | \"sqlite\" = \"pg\";' > server/db/index.ts",
"next:build": "next build",
"build:sqlite": "mkdir -p dist && next build && node esbuild.mjs -e server/index.ts -o dist/server.mjs && node esbuild.mjs -e server/setup/migrationsSqlite.ts -o dist/migrations.mjs",
"build:pg": "mkdir -p dist && next build && node esbuild.mjs -e server/index.ts -o dist/server.mjs && node esbuild.mjs -e server/setup/migrationsPg.ts -o dist/migrations.mjs",
"set:sqlite": "echo 'export * from \"./sqlite\";\nexport const driver: \"pg\" | \"sqlite\" = \"sqlite\";' > server/db/index.ts && cp drizzle.sqlite.config.ts drizzle.config.ts && cp server/setup/migrationsSqlite.ts server/setup/migrations.ts",
"set:pg": "echo 'export * from \"./pg\";\nexport const driver: \"pg\" | \"sqlite\" = \"pg\";' > server/db/index.ts && cp drizzle.pg.config.ts drizzle.config.ts && cp server/setup/migrationsPg.ts server/setup/migrations.ts",
"build:next": "next build",
"build": "mkdir -p dist && next build && node esbuild.mjs -e server/index.ts -o dist/server.mjs && node esbuild.mjs -e server/setup/migrations.ts -o dist/migrations.mjs",
"start": "ENVIRONMENT=prod node dist/migrations.mjs && ENVIRONMENT=prod NODE_ENV=development node --enable-source-maps dist/server.mjs",
"email": "email dev --dir server/emails/templates --port 3005",
"build:cli": "node esbuild.mjs -e cli/index.ts -o dist/cli.mjs",
"format:check": "prettier --check .",
"format": "prettier --write ."
},
"dependencies": {
"@asteasolutions/zod-to-openapi": "8.2.0",
"@aws-sdk/client-s3": "3.955.0",
"@faker-js/faker": "10.1.0",
"@asteasolutions/zod-to-openapi": "8.4.1",
"@aws-sdk/client-s3": "3.1011.0",
"@faker-js/faker": "10.3.0",
"@headlessui/react": "2.2.9",
"@hookform/resolvers": "5.2.2",
"@monaco-editor/react": "4.7.0",
@@ -60,90 +59,83 @@
"@radix-ui/react-tabs": "1.1.13",
"@radix-ui/react-toast": "1.2.15",
"@radix-ui/react-tooltip": "1.2.8",
"@react-email/components": "1.0.2",
"@react-email/render": "2.0.0",
"@react-email/tailwind": "2.0.2",
"@simplewebauthn/browser": "13.2.2",
"@simplewebauthn/server": "13.2.2",
"@react-email/components": "1.0.8",
"@react-email/render": "2.0.4",
"@react-email/tailwind": "2.0.5",
"@simplewebauthn/browser": "13.3.0",
"@simplewebauthn/server": "13.3.0",
"@tailwindcss/forms": "0.5.11",
"@tanstack/react-query": "5.90.12",
"@tanstack/react-query": "5.90.21",
"@tanstack/react-table": "8.21.3",
"arctic": "3.7.0",
"axios": "1.13.2",
"axios": "1.13.5",
"better-sqlite3": "11.9.1",
"canvas-confetti": "1.9.4",
"class-variance-authority": "0.7.1",
"clsx": "2.1.1",
"cmdk": "1.1.1",
"cookie": "1.1.1",
"cookie-parser": "1.4.7",
"cookies": "0.9.1",
"cors": "2.8.5",
"cors": "2.8.6",
"crypto-js": "4.2.0",
"d3": "7.9.0",
"date-fns": "4.1.0",
"drizzle-orm": "0.45.1",
"eslint": "9.39.2",
"eslint-config-next": "16.1.0",
"express": "5.2.1",
"express-rate-limit": "8.2.1",
"glob": "13.0.0",
"express-rate-limit": "8.3.0",
"glob": "13.0.6",
"helmet": "8.1.0",
"http-errors": "2.0.1",
"i": "0.3.7",
"input-otp": "1.4.2",
"ioredis": "5.8.2",
"ioredis": "5.10.0",
"jmespath": "0.16.0",
"js-yaml": "4.1.1",
"jsonwebtoken": "9.0.3",
"lucide-react": "0.562.0",
"maxmind": "5.0.1",
"lucide-react": "0.577.0",
"maxmind": "5.0.5",
"moment": "2.30.1",
"next": "15.5.9",
"next-intl": "4.6.1",
"next": "15.5.14",
"next-intl": "4.8.3",
"next-themes": "0.4.6",
"nextjs-toploader": "3.9.17",
"node-cache": "5.1.2",
"node-fetch": "3.3.2",
"nodemailer": "7.0.11",
"npm": "11.7.0",
"nprogress": "0.2.0",
"nodemailer": "8.0.4",
"oslo": "1.2.1",
"pg": "8.16.3",
"posthog-node": "5.17.4",
"pg": "8.20.0",
"posthog-node": "5.28.0",
"qrcode.react": "4.2.0",
"react": "19.2.3",
"react-day-picker": "9.13.0",
"react-dom": "19.2.3",
"react": "19.2.4",
"react-day-picker": "9.14.0",
"react-dom": "19.2.4",
"react-easy-sort": "1.8.0",
"react-hook-form": "7.68.0",
"react-icons": "5.5.0",
"rebuild": "0.1.2",
"react-hook-form": "7.71.2",
"react-icons": "5.6.0",
"recharts": "2.15.4",
"reodotdev": "1.0.0",
"resend": "6.6.0",
"semver": "7.7.3",
"stripe": "20.1.0",
"reodotdev": "1.1.0",
"resend": "6.9.2",
"semver": "7.7.4",
"sshpk": "1.18.0",
"stripe": "20.4.1",
"swagger-ui-express": "5.0.1",
"tailwind-merge": "3.4.0",
"tailwind-merge": "3.5.0",
"topojson-client": "3.1.0",
"tw-animate-css": "1.4.0",
"use-debounce": "10.1.0",
"uuid": "13.0.0",
"vaul": "1.1.2",
"visionscarto-world-atlas": "1.0.0",
"winston": "3.19.0",
"winston-daily-rotate-file": "5.0.0",
"ws": "8.18.3",
"yaml": "2.8.2",
"ws": "8.19.0",
"yaml": "2.8.3",
"yargs": "18.0.0",
"zod": "4.2.1",
"zod": "4.3.6",
"zod-validation-error": "5.0.0"
},
"devDependencies": {
"@dotenvx/dotenvx": "1.51.2",
"@dotenvx/dotenvx": "1.54.1",
"@esbuild-plugins/tsconfig-paths": "0.1.2",
"@tailwindcss/postcss": "4.1.18",
"@tanstack/react-query-devtools": "5.91.1",
"@react-email/preview-server": "5.2.10",
"@tailwindcss/postcss": "4.2.2",
"@tanstack/react-query-devtools": "5.91.3",
"@types/better-sqlite3": "7.6.13",
"@types/cookie-parser": "1.4.10",
"@types/cors": "2.8.19",
@@ -152,30 +144,37 @@
"@types/express": "5.0.6",
"@types/express-session": "1.18.2",
"@types/jmespath": "0.15.2",
"@types/js-yaml": "4.0.9",
"@types/jsonwebtoken": "9.0.10",
"@types/node": "24.10.2",
"@types/nodemailer": "7.0.4",
"@types/node": "25.3.5",
"@types/nodemailer": "7.0.11",
"@types/nprogress": "0.2.3",
"@types/pg": "8.16.0",
"@types/react": "19.2.7",
"@types/pg": "8.18.0",
"@types/react": "19.2.14",
"@types/react-dom": "19.2.3",
"@types/semver": "7.7.1",
"@types/sshpk": "1.17.4",
"@types/swagger-ui-express": "4.1.8",
"@types/topojson-client": "3.1.5",
"@types/ws": "8.18.1",
"@types/yargs": "17.0.35",
"@types/js-yaml": "4.0.9",
"babel-plugin-react-compiler": "1.0.0",
"drizzle-kit": "0.31.8",
"esbuild": "0.27.2",
"drizzle-kit": "0.31.10",
"esbuild": "0.27.4",
"esbuild-node-externals": "1.20.1",
"postcss": "8.5.6",
"prettier": "3.7.4",
"react-email": "5.0.7",
"tailwindcss": "4.1.18",
"eslint": "10.0.3",
"eslint-config-next": "16.1.7",
"postcss": "8.5.8",
"prettier": "3.8.1",
"react-email": "5.2.10",
"tailwindcss": "4.2.2",
"tsc-alias": "1.8.16",
"tsx": "4.21.0",
"typescript": "5.9.3",
"typescript-eslint": "8.49.0"
"typescript-eslint": "8.56.1"
},
"overrides": {
"esbuild": "0.27.4",
"dompurify": "3.3.2"
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 484 KiB

After

Width:  |  Height:  |  Size: 588 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 421 KiB

After

Width:  |  Height:  |  Size: 569 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 484 KiB

After

Width:  |  Height:  |  Size: 588 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 396 KiB

After

Width:  |  Height:  |  Size: 2.4 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 597 KiB

After

Width:  |  Height:  |  Size: 274 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 73 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

+29 -23
View File
@@ -1,9 +1,10 @@
import { Request } from "express";
import { db } from "@server/db";
import { userActions, roleActions, userOrgs } from "@server/db";
import { and, eq } from "drizzle-orm";
import { userActions, roleActions } from "@server/db";
import { and, eq, inArray } from "drizzle-orm";
import createHttpError from "http-errors";
import HttpCode from "@server/types/HttpCode";
import { getUserOrgRoleIds } from "@server/lib/userOrgRoles";
export enum ActionsEnum {
createOrgUser = "createOrgUser",
@@ -19,6 +20,7 @@ export enum ActionsEnum {
getSite = "getSite",
listSites = "listSites",
updateSite = "updateSite",
resetSiteBandwidth = "resetSiteBandwidth",
reGenerateSecret = "reGenerateSecret",
createResource = "createResource",
deleteResource = "deleteResource",
@@ -52,6 +54,8 @@ export enum ActionsEnum {
listRoleResources = "listRoleResources",
// listRoleActions = "listRoleActions",
addUserRole = "addUserRole",
removeUserRole = "removeUserRole",
setUserOrgRoles = "setUserOrgRoles",
// addUserSite = "addUserSite",
// addUserAction = "addUserAction",
// removeUserAction = "removeUserAction",
@@ -78,6 +82,10 @@ export enum ActionsEnum {
updateSiteResource = "updateSiteResource",
createClient = "createClient",
deleteClient = "deleteClient",
archiveClient = "archiveClient",
unarchiveClient = "unarchiveClient",
blockClient = "blockClient",
unblockClient = "unblockClient",
updateClient = "updateClient",
listClients = "listClients",
getClient = "getClient",
@@ -104,6 +112,10 @@ export enum ActionsEnum {
listApiKeyActions = "listApiKeyActions",
listApiKeys = "listApiKeys",
getApiKey = "getApiKey",
createSiteProvisioningKey = "createSiteProvisioningKey",
listSiteProvisioningKeys = "listSiteProvisioningKeys",
updateSiteProvisioningKey = "updateSiteProvisioningKey",
deleteSiteProvisioningKey = "deleteSiteProvisioningKey",
getCertificate = "getCertificate",
restartCertificate = "restartCertificate",
billing = "billing",
@@ -125,7 +137,14 @@ export enum ActionsEnum {
getBlueprint = "getBlueprint",
applyBlueprint = "applyBlueprint",
viewLogs = "viewLogs",
exportLogs = "exportLogs"
exportLogs = "exportLogs",
listApprovals = "listApprovals",
updateApprovals = "updateApprovals",
signSshKey = "signSshKey",
createEventStreamingDestination = "createEventStreamingDestination",
updateEventStreamingDestination = "updateEventStreamingDestination",
deleteEventStreamingDestination = "deleteEventStreamingDestination",
listEventStreamingDestinations = "listEventStreamingDestinations"
}
export async function checkUserActionPermission(
@@ -146,29 +165,16 @@ export async function checkUserActionPermission(
}
try {
let userOrgRoleId = req.userOrgRoleId;
let userOrgRoleIds = req.userOrgRoleIds;
// If userOrgRoleId is not available on the request, fetch it
if (userOrgRoleId === undefined) {
const userOrgRole = await db
.select()
.from(userOrgs)
.where(
and(
eq(userOrgs.userId, userId),
eq(userOrgs.orgId, req.userOrgId!)
)
)
.limit(1);
if (userOrgRole.length === 0) {
if (userOrgRoleIds === undefined) {
userOrgRoleIds = await getUserOrgRoleIds(userId, req.userOrgId!);
if (userOrgRoleIds.length === 0) {
throw createHttpError(
HttpCode.FORBIDDEN,
"User does not have access to this organization"
);
}
userOrgRoleId = userOrgRole[0].roleId;
}
// Check if the user has direct permission for the action in the current org
@@ -179,7 +185,7 @@ export async function checkUserActionPermission(
and(
eq(userActions.userId, userId),
eq(userActions.actionId, actionId),
eq(userActions.orgId, req.userOrgId!) // TODO: we cant pass the org id if we are not checking the org
eq(userActions.orgId, req.userOrgId!)
)
)
.limit(1);
@@ -188,14 +194,14 @@ export async function checkUserActionPermission(
return true;
}
// If no direct permission, check role-based permission
// If no direct permission, check role-based permission (any of user's roles)
const roleActionPermission = await db
.select()
.from(roleActions)
.where(
and(
eq(roleActions.actionId, actionId),
eq(roleActions.roleId, userOrgRoleId!),
inArray(roleActions.roleId, userOrgRoleIds),
eq(roleActions.orgId, req.userOrgId!)
)
)
+16 -13
View File
@@ -1,26 +1,29 @@
import { db } from "@server/db";
import { and, eq } from "drizzle-orm";
import { and, eq, inArray } from "drizzle-orm";
import { roleResources, userResources } from "@server/db";
export async function canUserAccessResource({
userId,
resourceId,
roleId
roleIds
}: {
userId: string;
resourceId: number;
roleId: number;
roleIds: number[];
}): Promise<boolean> {
const roleResourceAccess = await db
.select()
.from(roleResources)
.where(
and(
eq(roleResources.resourceId, resourceId),
eq(roleResources.roleId, roleId)
)
)
.limit(1);
const roleResourceAccess =
roleIds.length > 0
? await db
.select()
.from(roleResources)
.where(
and(
eq(roleResources.resourceId, resourceId),
inArray(roleResources.roleId, roleIds)
)
)
.limit(1)
: [];
if (roleResourceAccess.length > 0) {
return true;
+48
View File
@@ -0,0 +1,48 @@
import { db } from "@server/db";
import { and, eq, inArray } from "drizzle-orm";
import { roleSiteResources, userSiteResources } from "@server/db";
export async function canUserAccessSiteResource({
userId,
resourceId,
roleIds
}: {
userId: string;
resourceId: number;
roleIds: number[];
}): Promise<boolean> {
const roleResourceAccess =
roleIds.length > 0
? await db
.select()
.from(roleSiteResources)
.where(
and(
eq(roleSiteResources.siteResourceId, resourceId),
inArray(roleSiteResources.roleId, roleIds)
)
)
.limit(1)
: [];
if (roleResourceAccess.length > 0) {
return true;
}
const userResourceAccess = await db
.select()
.from(userSiteResources)
.where(
and(
eq(userSiteResources.userId, userId),
eq(userSiteResources.siteResourceId, resourceId)
)
)
.limit(1);
if (userResourceAccess.length > 0) {
return true;
}
return false;
}
+17 -6
View File
@@ -3,7 +3,14 @@ import {
encodeHexLowerCase
} from "@oslojs/encoding";
import { sha256 } from "@oslojs/crypto/sha2";
import { resourceSessions, Session, sessions, User, users } from "@server/db";
import {
resourceSessions,
safeRead,
Session,
sessions,
User,
users
} from "@server/db";
import { db } from "@server/db";
import { eq, inArray } from "drizzle-orm";
import config from "@server/lib/config";
@@ -54,11 +61,15 @@ export async function validateSessionToken(
const sessionId = encodeHexLowerCase(
sha256(new TextEncoder().encode(token))
);
const result = await db
.select({ user: users, session: sessions })
.from(sessions)
.innerJoin(users, eq(sessions.userId, users.userId))
.where(eq(sessions.sessionId, sessionId));
const result = await safeRead((db) =>
db
.select({ user: users, session: sessions })
.from(sessions)
.innerJoin(users, eq(sessions.userId, users.userId))
.where(eq(sessions.sessionId, sessionId))
);
if (result.length < 1) {
return { session: null, user: null };
}
+13 -11
View File
@@ -1,7 +1,7 @@
import { encodeHexLowerCase } from "@oslojs/encoding";
import { sha256 } from "@oslojs/crypto/sha2";
import { resourceSessions, ResourceSession } from "@server/db";
import { db } from "@server/db";
import { db, safeRead } from "@server/db";
import { eq, and } from "drizzle-orm";
import config from "@server/lib/config";
@@ -66,15 +66,17 @@ export async function validateResourceSessionToken(
const sessionId = encodeHexLowerCase(
sha256(new TextEncoder().encode(token))
);
const result = await db
.select()
.from(resourceSessions)
.where(
and(
eq(resourceSessions.sessionId, sessionId),
eq(resourceSessions.resourceId, resourceId)
const result = await safeRead((db) =>
db
.select()
.from(resourceSessions)
.where(
and(
eq(resourceSessions.sessionId, sessionId),
eq(resourceSessions.resourceId, resourceId)
)
)
);
);
if (result.length < 1) {
return { resourceSession: null };
@@ -85,7 +87,7 @@ export async function validateResourceSessionToken(
if (Date.now() >= resourceSession.expiresAt) {
await db
.delete(resourceSessions)
.where(eq(resourceSessions.sessionId, resourceSessions.sessionId));
.where(eq(resourceSessions.sessionId, sessionId));
return { resourceSession: null };
} else if (
Date.now() >=
@@ -179,7 +181,7 @@ export function serializeResourceSessionCookie(
return `${cookieName}_s.${now}=${token}; HttpOnly; SameSite=Lax; Expires=${expiresAt.toUTCString()}; Path=/; Secure; Domain=${domain}`;
} else {
if (expiresAt === undefined) {
return `${cookieName}.${now}=${token}; HttpOnly; SameSite=Lax; Path=/; Domain=$domain}`;
return `${cookieName}.${now}=${token}; HttpOnly; SameSite=Lax; Path=/; Domain=${domain}`;
}
return `${cookieName}.${now}=${token}; HttpOnly; SameSite=Lax; Expires=${expiresAt.toUTCString()}; Path=/; Domain=${domain}`;
}
+8
View File
@@ -1,6 +1,14 @@
import { flushBandwidthToDb } from "@server/routers/newt/handleReceiveBandwidthMessage";
import { flushConnectionLogToDb } from "#dynamic/routers/newt";
import { flushSiteBandwidthToDb } from "@server/routers/gerbil/receiveBandwidth";
import { stopPingAccumulator } from "@server/routers/newt/pingAccumulator";
import { cleanup as wsCleanup } from "#dynamic/routers/ws";
async function cleanup() {
await stopPingAccumulator();
await flushBandwidthToDb();
await flushConnectionLogToDb();
await flushSiteBandwidthToDb();
await wsCleanup();
process.exit(0);
+4 -4
View File
@@ -56,15 +56,15 @@ Ensure drizzle-kit is installed.
You must have a connection string in your config file, as shown above.
```bash
npm run db:pg:generate
npm run db:pg:push
npm run db:generate
npm run db:push
```
### SQLite
```bash
npm run db:sqlite:generate
npm run db:sqlite:push
npm run db:generate
npm run db:push
```
## Build Time
+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",
+150
View File
@@ -0,0 +1,150 @@
{
"iPad1,1": "iPad",
"iPad2,1": "iPad 2",
"iPad2,2": "iPad 2",
"iPad2,3": "iPad 2",
"iPad2,4": "iPad 2",
"iPad3,1": "iPad 3rd Gen",
"iPad3,3": "iPad 3rd Gen",
"iPad3,2": "iPad 3rd Gen",
"iPad3,4": "iPad 4th Gen",
"iPad3,5": "iPad 4th Gen",
"iPad3,6": "iPad 4th Gen",
"iPad6,11": "iPad 9.7 5th Gen",
"iPad6,12": "iPad 9.7 5th Gen",
"iPad7,5": "iPad 9.7 6th Gen",
"iPad7,6": "iPad 9.7 6th Gen",
"iPad7,11": "iPad 10.2 7th Gen",
"iPad7,12": "iPad 10.2 7th Gen",
"iPad11,6": "iPad 10.2 8th Gen",
"iPad11,7": "iPad 10.2 8th Gen",
"iPad12,1": "iPad 10.2 9th Gen",
"iPad12,2": "iPad 10.2 9th Gen",
"iPad13,18": "iPad 10.9 10th Gen",
"iPad13,19": "iPad 10.9 10th Gen",
"iPad4,1": "iPad Air",
"iPad4,2": "iPad Air",
"iPad4,3": "iPad Air",
"iPad5,3": "iPad Air 2",
"iPad5,4": "iPad Air 2",
"iPad11,3": "iPad Air 3rd Gen",
"iPad11,4": "iPad Air 3rd Gen",
"iPad13,1": "iPad Air 4th Gen",
"iPad13,2": "iPad Air 4th Gen",
"iPad13,16": "iPad Air 5th Gen",
"iPad13,17": "iPad Air 5th Gen",
"iPad14,8": "iPad Air M2 11",
"iPad14,9": "iPad Air M2 11",
"iPad14,10": "iPad Air M2 13",
"iPad14,11": "iPad Air M2 13",
"iPad2,5": "iPad mini",
"iPad2,6": "iPad mini",
"iPad2,7": "iPad mini",
"iPad4,4": "iPad mini 2",
"iPad4,5": "iPad mini 2",
"iPad4,6": "iPad mini 2",
"iPad4,7": "iPad mini 3",
"iPad4,8": "iPad mini 3",
"iPad4,9": "iPad mini 3",
"iPad5,1": "iPad mini 4",
"iPad5,2": "iPad mini 4",
"iPad11,1": "iPad mini 5th Gen",
"iPad11,2": "iPad mini 5th Gen",
"iPad14,1": "iPad mini 6th Gen",
"iPad14,2": "iPad mini 6th Gen",
"iPad6,7": "iPad Pro 12.9",
"iPad6,8": "iPad Pro 12.9",
"iPad6,3": "iPad Pro 9.7",
"iPad6,4": "iPad Pro 9.7",
"iPad7,3": "iPad Pro 10.5",
"iPad7,4": "iPad Pro 10.5",
"iPad7,1": "iPad Pro 12.9",
"iPad7,2": "iPad Pro 12.9",
"iPad8,1": "iPad Pro 11",
"iPad8,2": "iPad Pro 11",
"iPad8,3": "iPad Pro 11",
"iPad8,4": "iPad Pro 11",
"iPad8,5": "iPad Pro 12.9",
"iPad8,6": "iPad Pro 12.9",
"iPad8,7": "iPad Pro 12.9",
"iPad8,8": "iPad Pro 12.9",
"iPad8,9": "iPad Pro 11",
"iPad8,10": "iPad Pro 11",
"iPad8,11": "iPad Pro 12.9",
"iPad8,12": "iPad Pro 12.9",
"iPad13,4": "iPad Pro 11",
"iPad13,5": "iPad Pro 11",
"iPad13,6": "iPad Pro 11",
"iPad13,7": "iPad Pro 11",
"iPad13,8": "iPad Pro 12.9",
"iPad13,9": "iPad Pro 12.9",
"iPad13,10": "iPad Pro 12.9",
"iPad13,11": "iPad Pro 12.9",
"iPad14,3": "iPad Pro 11",
"iPad14,4": "iPad Pro 11",
"iPad14,5": "iPad Pro 12.9",
"iPad14,6": "iPad Pro 12.9",
"iPad16,3": "iPad Pro M4 11",
"iPad16,4": "iPad Pro M4 11",
"iPad16,5": "iPad Pro M4 13",
"iPad16,6": "iPad Pro M4 13",
"iPhone1,1": "iPhone",
"iPhone1,2": "iPhone 3G",
"iPhone2,1": "iPhone 3GS",
"iPhone3,1": "iPhone 4",
"iPhone3,2": "iPhone 4",
"iPhone3,3": "iPhone 4",
"iPhone4,1": "iPhone 4S",
"iPhone5,1": "iPhone 5",
"iPhone5,2": "iPhone 5",
"iPhone5,3": "iPhone 5c",
"iPhone5,4": "iPhone 5c",
"iPhone6,1": "iPhone 5s",
"iPhone6,2": "iPhone 5s",
"iPhone7,2": "iPhone 6",
"iPhone7,1": "iPhone 6 Plus",
"iPhone8,1": "iPhone 6s",
"iPhone8,2": "iPhone 6s Plus",
"iPhone8,4": "iPhone SE",
"iPhone9,1": "iPhone 7",
"iPhone9,3": "iPhone 7",
"iPhone9,2": "iPhone 7 Plus",
"iPhone9,4": "iPhone 7 Plus",
"iPhone10,1": "iPhone 8",
"iPhone10,4": "iPhone 8",
"iPhone10,2": "iPhone 8 Plus",
"iPhone10,5": "iPhone 8 Plus",
"iPhone10,3": "iPhone X",
"iPhone10,6": "iPhone X",
"iPhone11,2": "iPhone Xs",
"iPhone11,6": "iPhone Xs Max",
"iPhone11,8": "iPhone XR",
"iPhone12,1": "iPhone 11",
"iPhone12,3": "iPhone 11 Pro",
"iPhone12,5": "iPhone 11 Pro Max",
"iPhone12,8": "iPhone SE",
"iPhone13,1": "iPhone 12 mini",
"iPhone13,2": "iPhone 12",
"iPhone13,3": "iPhone 12 Pro",
"iPhone13,4": "iPhone 12 Pro Max",
"iPhone14,4": "iPhone 13 mini",
"iPhone14,5": "iPhone 13",
"iPhone14,2": "iPhone 13 Pro",
"iPhone14,3": "iPhone 13 Pro Max",
"iPhone14,6": "iPhone SE",
"iPhone14,7": "iPhone 14",
"iPhone14,8": "iPhone 14 Plus",
"iPhone15,2": "iPhone 14 Pro",
"iPhone15,3": "iPhone 14 Pro Max",
"iPhone15,4": "iPhone 15",
"iPhone15,5": "iPhone 15 Plus",
"iPhone16,1": "iPhone 15 Pro",
"iPhone16,2": "iPhone 15 Pro Max",
"iPod1,1": "iPod touch Original",
"iPod2,1": "iPod touch 2nd",
"iPod3,1": "iPod touch 3rd Gen",
"iPod4,1": "iPod touch 4th",
"iPod5,1": "iPod touch 5th",
"iPod7,1": "iPod touch 6th Gen",
"iPod9,1": "iPod touch 7th Gen"
}
+201
View File
@@ -0,0 +1,201 @@
{
"PowerMac4,4": "eMac",
"PowerMac6,4": "eMac",
"PowerBook2,1": "iBook",
"PowerBook2,2": "iBook",
"PowerBook4,1": "iBook",
"PowerBook4,2": "iBook",
"PowerBook4,3": "iBook",
"PowerBook6,3": "iBook",
"PowerBook6,5": "iBook",
"PowerBook6,7": "iBook",
"iMac,1": "iMac",
"PowerMac2,1": "iMac",
"PowerMac2,2": "iMac",
"PowerMac4,1": "iMac",
"PowerMac4,2": "iMac",
"PowerMac4,5": "iMac",
"PowerMac6,1": "iMac",
"PowerMac6,3*": "iMac",
"PowerMac6,3": "iMac",
"PowerMac8,1": "iMac",
"PowerMac8,2": "iMac",
"PowerMac12,1": "iMac",
"iMac4,1": "iMac",
"iMac4,2": "iMac",
"iMac5,2": "iMac",
"iMac5,1": "iMac",
"iMac6,1": "iMac",
"iMac7,1": "iMac",
"iMac8,1": "iMac",
"iMac9,1": "iMac",
"iMac10,1": "iMac",
"iMac11,1": "iMac",
"iMac11,2": "iMac",
"iMac11,3": "iMac",
"iMac12,1": "iMac",
"iMac12,2": "iMac",
"iMac13,1": "iMac",
"iMac13,2": "iMac",
"iMac14,1": "iMac",
"iMac14,3": "iMac",
"iMac14,2": "iMac",
"iMac14,4": "iMac",
"iMac15,1": "iMac",
"iMac16,1": "iMac",
"iMac16,2": "iMac",
"iMac17,1": "iMac",
"iMac18,1": "iMac",
"iMac18,2": "iMac",
"iMac18,3": "iMac",
"iMac19,2": "iMac",
"iMac19,1": "iMac",
"iMac20,1": "iMac",
"iMac20,2": "iMac",
"iMac21,2": "iMac",
"iMac21,1": "iMac",
"iMacPro1,1": "iMac Pro",
"PowerMac10,1": "Mac mini",
"PowerMac10,2": "Mac mini",
"Macmini1,1": "Mac mini",
"Macmini2,1": "Mac mini",
"Macmini3,1": "Mac mini",
"Macmini4,1": "Mac mini",
"Macmini5,1": "Mac mini",
"Macmini5,2": "Mac mini",
"Macmini5,3": "Mac mini",
"Macmini6,1": "Mac mini",
"Macmini6,2": "Mac mini",
"Macmini7,1": "Mac mini",
"Macmini8,1": "Mac mini",
"ADP3,2": "Mac mini",
"Macmini9,1": "Mac mini",
"Mac14,3": "Mac mini",
"Mac14,12": "Mac mini",
"MacPro1,1*": "Mac Pro",
"MacPro2,1": "Mac Pro",
"MacPro3,1": "Mac Pro",
"MacPro4,1": "Mac Pro",
"MacPro5,1": "Mac Pro",
"MacPro6,1": "Mac Pro",
"MacPro7,1": "Mac Pro",
"N/A*": "Power Macintosh",
"PowerMac1,1": "Power Macintosh",
"PowerMac3,1": "Power Macintosh",
"PowerMac3,3": "Power Macintosh",
"PowerMac3,4": "Power Macintosh",
"PowerMac3,5": "Power Macintosh",
"PowerMac3,6": "Power Macintosh",
"Mac13,1": "Mac Studio",
"Mac13,2": "Mac Studio",
"MacBook1,1": "MacBook",
"MacBook2,1": "MacBook",
"MacBook3,1": "MacBook",
"MacBook4,1": "MacBook",
"MacBook5,1": "MacBook",
"MacBook5,2": "MacBook",
"MacBook6,1": "MacBook",
"MacBook7,1": "MacBook",
"MacBook8,1": "MacBook",
"MacBook9,1": "MacBook",
"MacBook10,1": "MacBook",
"MacBookAir1,1": "MacBook Air",
"MacBookAir2,1": "MacBook Air",
"MacBookAir3,1": "MacBook Air",
"MacBookAir3,2": "MacBook Air",
"MacBookAir4,1": "MacBook Air",
"MacBookAir4,2": "MacBook Air",
"MacBookAir5,1": "MacBook Air",
"MacBookAir5,2": "MacBook Air",
"MacBookAir6,1": "MacBook Air",
"MacBookAir6,2": "MacBook Air",
"MacBookAir7,1": "MacBook Air",
"MacBookAir7,2": "MacBook Air",
"MacBookAir8,1": "MacBook Air",
"MacBookAir8,2": "MacBook Air",
"MacBookAir9,1": "MacBook Air",
"MacBookAir10,1": "MacBook Air",
"Mac14,2": "MacBook Air",
"MacBookPro1,1": "MacBook Pro",
"MacBookPro1,2": "MacBook Pro",
"MacBookPro2,2": "MacBook Pro",
"MacBookPro2,1": "MacBook Pro",
"MacBookPro3,1": "MacBook Pro",
"MacBookPro4,1": "MacBook Pro",
"MacBookPro5,1": "MacBook Pro",
"MacBookPro5,2": "MacBook Pro",
"MacBookPro5,5": "MacBook Pro",
"MacBookPro5,4": "MacBook Pro",
"MacBookPro5,3": "MacBook Pro",
"MacBookPro7,1": "MacBook Pro",
"MacBookPro6,2": "MacBook Pro",
"MacBookPro6,1": "MacBook Pro",
"MacBookPro8,1": "MacBook Pro",
"MacBookPro8,2": "MacBook Pro",
"MacBookPro8,3": "MacBook Pro",
"MacBookPro9,2": "MacBook Pro",
"MacBookPro9,1": "MacBook Pro",
"MacBookPro10,1": "MacBook Pro",
"MacBookPro10,2": "MacBook Pro",
"MacBookPro11,1": "MacBook Pro",
"MacBookPro11,2": "MacBook Pro",
"MacBookPro11,3": "MacBook Pro",
"MacBookPro12,1": "MacBook Pro",
"MacBookPro11,4": "MacBook Pro",
"MacBookPro11,5": "MacBook Pro",
"MacBookPro13,1": "MacBook Pro",
"MacBookPro13,2": "MacBook Pro",
"MacBookPro13,3": "MacBook Pro",
"MacBookPro14,1": "MacBook Pro",
"MacBookPro14,2": "MacBook Pro",
"MacBookPro14,3": "MacBook Pro",
"MacBookPro15,2": "MacBook Pro",
"MacBookPro15,1": "MacBook Pro",
"MacBookPro15,3": "MacBook Pro",
"MacBookPro15,4": "MacBook Pro",
"MacBookPro16,1": "MacBook Pro",
"MacBookPro16,3": "MacBook Pro",
"MacBookPro16,2": "MacBook Pro",
"MacBookPro16,4": "MacBook Pro",
"MacBookPro17,1": "MacBook Pro",
"MacBookPro18,3": "MacBook Pro",
"MacBookPro18,4": "MacBook Pro",
"MacBookPro18,1": "MacBook Pro",
"MacBookPro18,2": "MacBook Pro",
"Mac14,7": "MacBook Pro",
"Mac14,9": "MacBook Pro",
"Mac14,5": "MacBook Pro",
"Mac14,10": "MacBook Pro",
"Mac14,6": "MacBook Pro",
"PowerMac1,2": "Power Macintosh",
"PowerMac5,1": "Power Macintosh",
"PowerMac7,2": "Power Macintosh",
"PowerMac7,3": "Power Macintosh",
"PowerMac9,1": "Power Macintosh",
"PowerMac11,2": "Power Macintosh",
"PowerBook1,1": "PowerBook",
"PowerBook3,1": "PowerBook",
"PowerBook3,2": "PowerBook",
"PowerBook3,3": "PowerBook",
"PowerBook3,4": "PowerBook",
"PowerBook3,5": "PowerBook",
"PowerBook6,1": "PowerBook",
"PowerBook5,1": "PowerBook",
"PowerBook6,2": "PowerBook",
"PowerBook5,2": "PowerBook",
"PowerBook5,3": "PowerBook",
"PowerBook6,4": "PowerBook",
"PowerBook5,4": "PowerBook",
"PowerBook5,5": "PowerBook",
"PowerBook6,8": "PowerBook",
"PowerBook5,6": "PowerBook",
"PowerBook5,7": "PowerBook",
"PowerBook5,8": "PowerBook",
"PowerBook5,9": "PowerBook",
"RackMac1,1": "Xserve",
"RackMac1,2": "Xserve",
"RackMac3,1": "Xserve",
"Xserve1,1": "Xserve",
"Xserve2,1": "Xserve",
"Xserve3,1": "Xserve"
}
+3
View File
@@ -0,0 +1,3 @@
import { runMigrations } from "./";
await runMigrations();
+44
View File
@@ -16,6 +16,24 @@ if (!dev) {
}
export const names = JSON.parse(readFileSync(file, "utf-8"));
// Load iOS and Mac model mappings
let iosModelsFile: string;
let macModelsFile: string;
if (!dev) {
iosModelsFile = join(__DIRNAME, "ios_models.json");
macModelsFile = join(__DIRNAME, "mac_models.json");
} else {
iosModelsFile = join("server/db/ios_models.json");
macModelsFile = join("server/db/mac_models.json");
}
const iosModels: Record<string, string> = JSON.parse(
readFileSync(iosModelsFile, "utf-8")
);
const macModels: Record<string, string> = JSON.parse(
readFileSync(macModelsFile, "utf-8")
);
export async function getUniqueClientName(orgId: string): Promise<string> {
let loops = 0;
while (true) {
@@ -159,3 +177,29 @@ export function generateName(): string {
// clean out any non-alphanumeric characters except for dashes
return name.replace(/[^a-z0-9-]/g, "");
}
export function getMacDeviceName(macIdentifier?: string | null): string | null {
if (macIdentifier && macModels[macIdentifier]) {
return macModels[macIdentifier];
}
return null;
}
export function getIosDeviceName(iosIdentifier?: string | null): string | null {
if (iosIdentifier && iosModels[iosIdentifier]) {
return iosModels[iosIdentifier];
}
return null;
}
export function getUserDeviceName(
model: string | null,
fallBack: string | null
): string {
return (
getMacDeviceName(model) ||
getIosDeviceName(model) ||
fallBack ||
"Unknown Device"
);
}
+20 -13
View File
@@ -1,7 +1,7 @@
import { drizzle as DrizzlePostgres } from "drizzle-orm/node-postgres";
import { Pool } from "pg";
import { readConfigFile } from "@server/lib/readConfigFile";
import { withReplicas } from "drizzle-orm/pg-core";
import { createPool } from "./poolConfig";
function createDb() {
const config = readConfigFile();
@@ -39,12 +39,17 @@ function createDb() {
// Create connection pools instead of individual connections
const poolConfig = config.postgres.pool;
const primaryPool = new Pool({
const maxConnections = poolConfig?.max_connections || 20;
const idleTimeoutMs = poolConfig?.idle_timeout_ms || 30000;
const connectionTimeoutMs = poolConfig?.connection_timeout_ms || 5000;
const primaryPool = createPool(
connectionString,
max: poolConfig?.max_connections || 20,
idleTimeoutMillis: poolConfig?.idle_timeout_ms || 30000,
connectionTimeoutMillis: poolConfig?.connection_timeout_ms || 5000
});
maxConnections,
idleTimeoutMs,
connectionTimeoutMs,
"primary"
);
const replicas = [];
@@ -55,14 +60,15 @@ function createDb() {
})
);
} else {
const maxReplicaConnections = poolConfig?.max_replica_connections || 20;
for (const conn of replicaConnections) {
const replicaPool = new Pool({
connectionString: conn.connection_string,
max: poolConfig?.max_replica_connections || 20,
idleTimeoutMillis: poolConfig?.idle_timeout_ms || 30000,
connectionTimeoutMillis:
poolConfig?.connection_timeout_ms || 5000
});
const replicaPool = createPool(
conn.connection_string,
maxReplicaConnections,
idleTimeoutMs,
connectionTimeoutMs,
"replica"
);
replicas.push(
DrizzlePostgres(replicaPool, {
logger: process.env.QUERY_LOGGING == "true"
@@ -85,3 +91,4 @@ export const primaryDb = db.$primary;
export type Transaction = Parameters<
Parameters<(typeof db)["transaction"]>[0]
>[0];
export const DB_TYPE: "pg" | "sqlite" = "pg";
+3
View File
@@ -1,3 +1,6 @@
export * from "./driver";
export * from "./logsDriver";
export * from "./safeRead";
export * from "./schema/schema";
export * from "./schema/privateSchema";
export * from "./migrate";
+94
View File
@@ -0,0 +1,94 @@
import { drizzle as DrizzlePostgres } from "drizzle-orm/node-postgres";
import { readConfigFile } from "@server/lib/readConfigFile";
import { withReplicas } from "drizzle-orm/pg-core";
import { build } from "@server/build";
import { db as mainDb, primaryDb as mainPrimaryDb } from "./driver";
import { createPool } from "./poolConfig";
function createLogsDb() {
// Only use separate logs database in SaaS builds
if (build !== "saas") {
return mainDb;
}
const config = readConfigFile();
// Merge configs, prioritizing private config
const logsConfig = config.postgres_logs;
// Check environment variable first
let connectionString = process.env.POSTGRES_LOGS_CONNECTION_STRING;
let replicaConnections: Array<{ connection_string: string }> = [];
if (!connectionString && logsConfig) {
connectionString = logsConfig.connection_string;
replicaConnections = logsConfig.replicas || [];
}
// If POSTGRES_LOGS_REPLICA_CONNECTION_STRINGS is set, use it
if (process.env.POSTGRES_LOGS_REPLICA_CONNECTION_STRINGS) {
replicaConnections =
process.env.POSTGRES_LOGS_REPLICA_CONNECTION_STRINGS.split(",").map(
(conn) => ({
connection_string: conn.trim()
})
);
}
// If no logs database is configured, fall back to main database
if (!connectionString) {
return mainDb;
}
// Create separate connection pool for logs database
const poolConfig = logsConfig?.pool || config.postgres?.pool;
const maxConnections = poolConfig?.max_connections || 20;
const idleTimeoutMs = poolConfig?.idle_timeout_ms || 30000;
const connectionTimeoutMs = poolConfig?.connection_timeout_ms || 5000;
const primaryPool = createPool(
connectionString,
maxConnections,
idleTimeoutMs,
connectionTimeoutMs,
"logs-primary"
);
const replicas = [];
if (!replicaConnections.length) {
replicas.push(
DrizzlePostgres(primaryPool, {
logger: process.env.QUERY_LOGGING == "true"
})
);
} else {
const maxReplicaConnections =
poolConfig?.max_replica_connections || 20;
for (const conn of replicaConnections) {
const replicaPool = createPool(
conn.connection_string,
maxReplicaConnections,
idleTimeoutMs,
connectionTimeoutMs,
"logs-replica"
);
replicas.push(
DrizzlePostgres(replicaPool, {
logger: process.env.QUERY_LOGGING == "true"
})
);
}
}
return withReplicas(
DrizzlePostgres(primaryPool, {
logger: process.env.QUERY_LOGGING == "true"
}),
replicas as any
);
}
export const logsDb = createLogsDb();
export default logsDb;
export const primaryLogsDb = logsDb.$primary;
+1 -3
View File
@@ -4,7 +4,7 @@ import path from "path";
const migrationsFolder = path.join("server/migrations");
const runMigrations = async () => {
export const runMigrations = async () => {
console.log("Running migrations...");
try {
await migrate(db as any, {
@@ -17,5 +17,3 @@ const runMigrations = async () => {
process.exit(1);
}
};
runMigrations();
+63
View File
@@ -0,0 +1,63 @@
import { Pool, PoolConfig } from "pg";
import logger from "@server/logger";
export function createPoolConfig(
connectionString: string,
maxConnections: number,
idleTimeoutMs: number,
connectionTimeoutMs: number
): PoolConfig {
return {
connectionString,
max: maxConnections,
idleTimeoutMillis: idleTimeoutMs,
connectionTimeoutMillis: connectionTimeoutMs,
// TCP keepalive to prevent silent connection drops by NAT gateways,
// load balancers, and other intermediate network devices (e.g. AWS
// NAT Gateway drops idle TCP connections after ~350s)
keepAlive: true,
keepAliveInitialDelayMillis: 10000, // send first keepalive after 10s of idle
// Allow connections to be released and recreated more aggressively
// to avoid stale connections building up
allowExitOnIdle: false
};
}
export function attachPoolErrorHandlers(pool: Pool, label: string): void {
pool.on("error", (err) => {
// This catches errors on idle clients in the pool. Without this
// handler an unexpected disconnect would crash the process.
logger.error(
`Unexpected error on idle ${label} database client: ${err.message}`
);
});
pool.on("connect", (client) => {
// Set a statement timeout on every new connection so a single slow
// query can't block the pool forever
client.query("SET statement_timeout = '30s'").catch((err: Error) => {
logger.warn(
`Failed to set statement_timeout on ${label} client: ${err.message}`
);
});
});
}
export function createPool(
connectionString: string,
maxConnections: number,
idleTimeoutMs: number,
connectionTimeoutMs: number,
label: string
): Pool {
const pool = new Pool(
createPoolConfig(
connectionString,
maxConnections,
idleTimeoutMs,
connectionTimeoutMs
)
);
attachPoolErrorHandlers(pool, label);
return pool;
}
+24
View File
@@ -0,0 +1,24 @@
import { db, primaryDb } from "./driver";
/**
* Runs a read query with replica fallback for Postgres.
* Executes the query against the replica first (when replicas exist).
* If the query throws or returns no data (null, undefined, or empty array),
* runs the same query against the primary.
*/
export async function safeRead<T>(
query: (d: typeof db | typeof primaryDb) => Promise<T>
): Promise<T> {
try {
const result = await query(db);
if (result === undefined || result === null) {
return query(primaryDb);
}
if (Array.isArray(result) && result.length === 0) {
return query(primaryDb);
}
return result;
} catch {
return query(primaryDb);
}
}
+190 -4
View File
@@ -7,10 +7,22 @@ import {
bigint,
real,
text,
index
index,
primaryKey,
uniqueIndex
} from "drizzle-orm/pg-core";
import { InferSelectModel } from "drizzle-orm";
import { domains, orgs, targets, users, exitNodes, sessions } from "./schema";
import {
domains,
orgs,
targets,
users,
exitNodes,
sessions,
clients,
siteResources,
sites
} from "./schema";
export const certificates = pgTable("certificates", {
certId: serial("certId").primaryKey(),
@@ -74,11 +86,16 @@ export const subscriptions = pgTable("subscriptions", {
canceledAt: bigint("canceledAt", { mode: "number" }),
createdAt: bigint("createdAt", { mode: "number" }).notNull(),
updatedAt: bigint("updatedAt", { mode: "number" }),
billingCycleAnchor: bigint("billingCycleAnchor", { mode: "number" })
version: integer("version"),
billingCycleAnchor: bigint("billingCycleAnchor", { mode: "number" }),
type: varchar("type", { length: 50 }) // tier1, tier2, tier3, or license
});
export const subscriptionItems = pgTable("subscriptionItems", {
subscriptionItemId: serial("subscriptionItemId").primaryKey(),
stripeSubscriptionItemId: varchar("stripeSubscriptionItemId", {
length: 255
}),
subscriptionId: varchar("subscriptionId", { length: 255 })
.notNull()
.references(() => subscriptions.subscriptionId, {
@@ -86,6 +103,7 @@ export const subscriptionItems = pgTable("subscriptionItems", {
}),
planId: varchar("planId", { length: 255 }).notNull(),
priceId: varchar("priceId", { length: 255 }),
featureId: varchar("featureId", { length: 255 }),
meterId: varchar("meterId", { length: 255 }),
unitAmount: real("unitAmount"),
tiers: text("tiers"),
@@ -128,6 +146,7 @@ export const limits = pgTable("limits", {
})
.notNull(),
value: real("value"),
override: boolean("override").default(false),
description: text("description")
});
@@ -206,7 +225,7 @@ export const loginPageOrg = pgTable("loginPageOrg", {
export const loginPageBranding = pgTable("loginPageBranding", {
loginPageBrandingId: serial("loginPageBrandingId").primaryKey(),
logoUrl: text("logoUrl").notNull(),
logoUrl: text("logoUrl"),
logoWidth: integer("logoWidth").notNull(),
logoHeight: integer("logoHeight").notNull(),
primaryColor: text("primaryColor"),
@@ -273,6 +292,7 @@ export const accessAuditLog = pgTable(
actor: varchar("actor", { length: 255 }),
actorId: varchar("actorId", { length: 255 }),
resourceId: integer("resourceId"),
siteResourceId: integer("siteResourceId"),
ip: varchar("ip", { length: 45 }),
type: varchar("type", { length: 100 }).notNull(),
action: boolean("action").notNull(),
@@ -289,6 +309,156 @@ export const accessAuditLog = pgTable(
]
);
export const connectionAuditLog = pgTable(
"connectionAuditLog",
{
id: serial("id").primaryKey(),
sessionId: text("sessionId").notNull(),
siteResourceId: integer("siteResourceId").references(
() => siteResources.siteResourceId,
{ onDelete: "cascade" }
),
orgId: text("orgId").references(() => orgs.orgId, {
onDelete: "cascade"
}),
siteId: integer("siteId").references(() => sites.siteId, {
onDelete: "cascade"
}),
clientId: integer("clientId").references(() => clients.clientId, {
onDelete: "cascade"
}),
userId: text("userId").references(() => users.userId, {
onDelete: "cascade"
}),
sourceAddr: text("sourceAddr").notNull(),
destAddr: text("destAddr").notNull(),
protocol: text("protocol").notNull(),
startedAt: integer("startedAt").notNull(),
endedAt: integer("endedAt"),
bytesTx: integer("bytesTx"),
bytesRx: integer("bytesRx")
},
(table) => [
index("idx_accessAuditLog_startedAt").on(table.startedAt),
index("idx_accessAuditLog_org_startedAt").on(
table.orgId,
table.startedAt
),
index("idx_accessAuditLog_siteResourceId").on(table.siteResourceId)
]
);
export const approvals = pgTable("approvals", {
approvalId: serial("approvalId").primaryKey(),
timestamp: integer("timestamp").notNull(), // this is EPOCH time in seconds
orgId: varchar("orgId")
.references(() => orgs.orgId, {
onDelete: "cascade"
})
.notNull(),
clientId: integer("clientId").references(() => clients.clientId, {
onDelete: "cascade"
}), // clients reference user devices (in this case)
userId: varchar("userId")
.references(() => users.userId, {
// optionally tied to a user and in this case delete when the user deletes
onDelete: "cascade"
})
.notNull(),
decision: varchar("decision")
.$type<"approved" | "denied" | "pending">()
.default("pending")
.notNull(),
type: varchar("type")
.$type<"user_device" /*| 'proxy' // for later */>()
.notNull()
});
export const bannedEmails = pgTable("bannedEmails", {
email: varchar("email", { length: 255 }).primaryKey()
});
export const bannedIps = pgTable("bannedIps", {
ip: varchar("ip", { length: 255 }).primaryKey()
});
export const siteProvisioningKeys = pgTable("siteProvisioningKeys", {
siteProvisioningKeyId: varchar("siteProvisioningKeyId", {
length: 255
}).primaryKey(),
name: varchar("name", { length: 255 }).notNull(),
siteProvisioningKeyHash: text("siteProvisioningKeyHash").notNull(),
lastChars: varchar("lastChars", { length: 4 }).notNull(),
createdAt: varchar("dateCreated", { length: 255 }).notNull(),
lastUsed: varchar("lastUsed", { length: 255 }),
maxBatchSize: integer("maxBatchSize"), // null = no limit
numUsed: integer("numUsed").notNull().default(0),
validUntil: varchar("validUntil", { length: 255 }),
approveNewSites: boolean("approveNewSites").notNull().default(true)
});
export const siteProvisioningKeyOrg = pgTable(
"siteProvisioningKeyOrg",
{
siteProvisioningKeyId: varchar("siteProvisioningKeyId", {
length: 255
})
.notNull()
.references(() => siteProvisioningKeys.siteProvisioningKeyId, {
onDelete: "cascade"
}),
orgId: varchar("orgId", { length: 255 })
.notNull()
.references(() => orgs.orgId, { onDelete: "cascade" })
},
(table) => [
primaryKey({
columns: [table.siteProvisioningKeyId, table.orgId]
})
]
);
export const eventStreamingDestinations = pgTable(
"eventStreamingDestinations",
{
destinationId: serial("destinationId").primaryKey(),
orgId: varchar("orgId", { length: 255 })
.notNull()
.references(() => orgs.orgId, { onDelete: "cascade" }),
sendConnectionLogs: boolean("sendConnectionLogs").notNull().default(false),
sendRequestLogs: boolean("sendRequestLogs").notNull().default(false),
sendActionLogs: boolean("sendActionLogs").notNull().default(false),
sendAccessLogs: boolean("sendAccessLogs").notNull().default(false),
type: varchar("type", { length: 50 }).notNull(), // e.g. "http", "kafka", etc.
config: text("config").notNull(), // JSON string with the configuration for the destination
enabled: boolean("enabled").notNull().default(true),
createdAt: bigint("createdAt", { mode: "number" }).notNull(),
updatedAt: bigint("updatedAt", { mode: "number" }).notNull()
}
);
export const eventStreamingCursors = pgTable(
"eventStreamingCursors",
{
cursorId: serial("cursorId").primaryKey(),
destinationId: integer("destinationId")
.notNull()
.references(() => eventStreamingDestinations.destinationId, {
onDelete: "cascade"
}),
logType: varchar("logType", { length: 50 }).notNull(), // "request" | "action" | "access" | "connection"
lastSentId: bigint("lastSentId", { mode: "number" }).notNull().default(0),
lastSentAt: bigint("lastSentAt", { mode: "number" }) // epoch milliseconds, null if never sent
},
(table) => [
uniqueIndex("idx_eventStreamingCursors_dest_type").on(
table.destinationId,
table.logType
)
]
);
export type Approval = InferSelectModel<typeof approvals>;
export type Limit = InferSelectModel<typeof limits>;
export type Account = InferSelectModel<typeof account>;
export type Certificate = InferSelectModel<typeof certificates>;
@@ -309,3 +479,19 @@ export type LoginPage = InferSelectModel<typeof loginPage>;
export type LoginPageBranding = InferSelectModel<typeof loginPageBranding>;
export type ActionAuditLog = InferSelectModel<typeof actionAuditLog>;
export type AccessAuditLog = InferSelectModel<typeof accessAuditLog>;
export type ConnectionAuditLog = InferSelectModel<typeof connectionAuditLog>;
export type SessionTransferToken = InferSelectModel<
typeof sessionTransferToken
>;
export type BannedEmail = InferSelectModel<typeof bannedEmails>;
export type BannedIp = InferSelectModel<typeof bannedIps>;
export type SiteProvisioningKey = InferSelectModel<typeof siteProvisioningKeys>;
export type SiteProvisioningKeyOrg = InferSelectModel<
typeof siteProvisioningKeyOrg
>;
export type EventStreamingDestination = InferSelectModel<
typeof eventStreamingDestinations
>;
export type EventStreamingCursor = InferSelectModel<
typeof eventStreamingCursors
>;
+249 -44
View File
@@ -1,18 +1,18 @@
import {
pgTable,
serial,
varchar,
boolean,
integer,
bigint,
real,
text,
index,
uniqueIndex
} from "drizzle-orm/pg-core";
import { InferSelectModel } from "drizzle-orm";
import { randomUUID } from "crypto";
import { alias } from "yargs";
import { InferSelectModel } from "drizzle-orm";
import {
bigint,
boolean,
index,
integer,
pgTable,
primaryKey,
real,
serial,
text,
unique,
varchar
} from "drizzle-orm/pg-core";
export const domains = pgTable("domains", {
domainId: varchar("domainId").primaryKey(),
@@ -24,7 +24,8 @@ export const domains = pgTable("domains", {
tries: integer("tries").notNull().default(0),
certResolver: varchar("certResolver"),
customCertResolver: varchar("customCertResolver"),
preferWildcardCert: boolean("preferWildcardCert")
preferWildcardCert: boolean("preferWildcardCert"),
errorMessage: text("errorMessage")
});
export const dnsRecords = pgTable("dnsRecords", {
@@ -55,7 +56,14 @@ export const orgs = pgTable("orgs", {
.default(0),
settingsLogRetentionDaysAction: integer("settingsLogRetentionDaysAction") // where 0 = dont keep logs and -1 = keep forever and 9001 = end of the following year
.notNull()
.default(0)
.default(0),
settingsLogRetentionDaysConnection: integer("settingsLogRetentionDaysConnection") // where 0 = dont keep logs and -1 = keep forever and 9001 = end of the following year
.notNull()
.default(0),
sshCaPrivateKey: text("sshCaPrivateKey"), // Encrypted SSH CA private key (PEM format)
sshCaPublicKey: text("sshCaPublicKey"), // SSH CA public key (OpenSSH format)
isBillingOrg: boolean("isBillingOrg"),
billingOrgId: varchar("billingOrgId")
});
export const orgDomains = pgTable("orgDomains", {
@@ -86,12 +94,14 @@ export const sites = pgTable("sites", {
lastBandwidthUpdate: varchar("lastBandwidthUpdate"),
type: varchar("type").notNull(), // "newt" or "wireguard"
online: boolean("online").notNull().default(false),
lastPing: integer("lastPing"),
address: varchar("address"),
endpoint: varchar("endpoint"),
publicKey: varchar("publicKey"),
lastHolePunch: bigint("lastHolePunch", { mode: "number" }),
listenPort: integer("listenPort"),
dockerSocketEnabled: boolean("dockerSocketEnabled").notNull().default(true)
dockerSocketEnabled: boolean("dockerSocketEnabled").notNull().default(true),
status: varchar("status").$type<"pending" | "approved">().default("approved")
});
export const resources = pgTable("resources", {
@@ -134,13 +144,16 @@ 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"),
postAuthPath: text("postAuthPath")
});
export const targets = pgTable("targets", {
@@ -185,7 +198,9 @@ export const targetHealthCheck = pgTable("targetHealthCheck", {
hcFollowRedirects: boolean("hcFollowRedirects").default(true),
hcMethod: varchar("hcMethod").default("GET"),
hcStatus: integer("hcStatus"), // http code
hcHealth: text("hcHealth").default("unknown"), // "unknown", "healthy", "unhealthy"
hcHealth: text("hcHealth")
.$type<"unknown" | "healthy" | "unhealthy">()
.default("unknown"), // "unknown", "healthy", "unhealthy"
hcTlsServerName: text("hcTlsServerName")
});
@@ -215,7 +230,7 @@ export const siteResources = pgTable("siteResources", {
.references(() => orgs.orgId, { onDelete: "cascade" }),
niceId: varchar("niceId").notNull(),
name: varchar("name").notNull(),
mode: varchar("mode").notNull(), // "host" | "cidr" | "port"
mode: varchar("mode").$type<"host" | "cidr">().notNull(), // "host" | "cidr" | "port"
protocol: varchar("protocol"), // only for port mode
proxyPort: integer("proxyPort"), // only for port mode
destinationPort: integer("destinationPort"), // only for port mode
@@ -223,9 +238,13 @@ export const siteResources = pgTable("siteResources", {
enabled: boolean("enabled").notNull().default(true),
alias: varchar("alias"),
aliasAddress: varchar("aliasAddress"),
tcpPortRangeString: varchar("tcpPortRangeString"),
udpPortRangeString: varchar("udpPortRangeString"),
disableIcmp: boolean("disableIcmp").notNull().default(false)
tcpPortRangeString: varchar("tcpPortRangeString").notNull().default("*"),
udpPortRangeString: varchar("udpPortRangeString").notNull().default("*"),
disableIcmp: boolean("disableIcmp").notNull().default(false),
authDaemonPort: integer("authDaemonPort").default(22123),
authDaemonMode: varchar("authDaemonMode", { length: 32 })
.$type<"site" | "remote">()
.default("site")
});
export const clientSiteResources = pgTable("clientSiteResources", {
@@ -272,8 +291,10 @@ export const users = pgTable("user", {
dateCreated: varchar("dateCreated").notNull(),
termsAcceptedTimestamp: varchar("termsAcceptedTimestamp"),
termsVersion: varchar("termsVersion"),
marketingEmailConsent: boolean("marketingEmailConsent").default(false),
serverAdmin: boolean("serverAdmin").notNull().default(false),
lastPasswordChange: bigint("lastPasswordChange", { mode: "number" })
lastPasswordChange: bigint("lastPasswordChange", { mode: "number" }),
locale: varchar("locale")
});
export const newts = pgTable("newt", {
@@ -321,11 +342,9 @@ export const userOrgs = pgTable("userOrgs", {
onDelete: "cascade"
})
.notNull(),
roleId: integer("roleId")
.notNull()
.references(() => roles.roleId),
isOwner: boolean("isOwner").notNull().default(false),
autoProvisioned: boolean("autoProvisioned").default(false)
autoProvisioned: boolean("autoProvisioned").default(false),
pamUsername: varchar("pamUsername") // cleaned username for ssh and such
});
export const emailVerificationCodes = pgTable("emailVerificationCodes", {
@@ -363,9 +382,30 @@ export const roles = pgTable("roles", {
.notNull(),
isAdmin: boolean("isAdmin"),
name: varchar("name").notNull(),
description: varchar("description")
description: varchar("description"),
requireDeviceApproval: boolean("requireDeviceApproval").default(false),
sshSudoMode: varchar("sshSudoMode", { length: 32 }).default("none"), // "none" | "full" | "commands"
sshSudoCommands: text("sshSudoCommands").default("[]"),
sshCreateHomeDir: boolean("sshCreateHomeDir").default(true),
sshUnixGroups: text("sshUnixGroups").default("[]")
});
export const userOrgRoles = pgTable(
"userOrgRoles",
{
userId: varchar("userId")
.notNull()
.references(() => users.userId, { onDelete: "cascade" }),
orgId: varchar("orgId")
.notNull()
.references(() => orgs.orgId, { onDelete: "cascade" }),
roleId: integer("roleId")
.notNull()
.references(() => roles.roleId, { onDelete: "cascade" })
},
(t) => [unique().on(t.userId, t.orgId, t.roleId)]
);
export const roleActions = pgTable("roleActions", {
roleId: integer("roleId")
.notNull()
@@ -433,12 +473,22 @@ export const userInvites = pgTable("userInvites", {
.references(() => orgs.orgId, { onDelete: "cascade" }),
email: varchar("email").notNull(),
expiresAt: bigint("expiresAt", { mode: "number" }).notNull(),
tokenHash: varchar("token").notNull(),
roleId: integer("roleId")
.notNull()
.references(() => roles.roleId, { onDelete: "cascade" })
tokenHash: varchar("token").notNull()
});
export const userInviteRoles = pgTable(
"userInviteRoles",
{
inviteId: varchar("inviteId")
.notNull()
.references(() => userInvites.inviteId, { onDelete: "cascade" }),
roleId: integer("roleId")
.notNull()
.references(() => roles.roleId, { onDelete: "cascade" })
},
(t) => [primaryKey({ columns: [t.inviteId, t.roleId] })]
);
export const resourcePincode = pgTable("resourcePincode", {
pincodeId: serial("pincodeId").primaryKey(),
resourceId: integer("resourceId")
@@ -464,13 +514,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(),
@@ -580,7 +639,8 @@ export const idp = pgTable("idp", {
type: varchar("type").notNull(),
defaultRoleMapping: varchar("defaultRoleMapping"),
defaultOrgMapping: varchar("defaultOrgMapping"),
autoProvision: boolean("autoProvision").notNull().default(false)
autoProvision: boolean("autoProvision").notNull().default(false),
tags: text("tags")
});
export const idpOidcConfig = pgTable("idpOidcConfig", {
@@ -677,7 +737,12 @@ export const clients = pgTable("clients", {
online: boolean("online").notNull().default(false),
// endpoint: varchar("endpoint"),
lastHolePunch: integer("lastHolePunch"),
maxConnections: integer("maxConnections")
maxConnections: integer("maxConnections"),
archived: boolean("archived").notNull().default(false),
blocked: boolean("blocked").notNull().default(false),
approvalState: varchar("approvalState").$type<
"pending" | "approved" | "denied"
>()
});
export const clientSitesAssociationsCache = pgTable(
@@ -687,6 +752,7 @@ export const clientSitesAssociationsCache = pgTable(
.notNull(),
siteId: integer("siteId").notNull(),
isRelayed: boolean("isRelayed").notNull().default(false),
isJitMode: boolean("isJitMode").notNull().default(false),
endpoint: varchar("endpoint"),
publicKey: varchar("publicKey") // this will act as the session's public key for hole punching so we can track when it changes
}
@@ -701,6 +767,16 @@ export const clientSiteResourcesAssociationsCache = pgTable(
}
);
export const clientPostureSnapshots = pgTable("clientPostureSnapshots", {
snapshotId: serial("snapshotId").primaryKey(),
clientId: integer("clientId").references(() => clients.clientId, {
onDelete: "cascade"
}),
collectedAt: integer("collectedAt").notNull()
});
export const olms = pgTable("olms", {
olmId: varchar("id").primaryKey(),
secretHash: varchar("secretHash").notNull(),
@@ -715,7 +791,118 @@ export const olms = pgTable("olms", {
userId: text("userId").references(() => users.userId, {
// optionally tied to a user and in this case delete when the user deletes
onDelete: "cascade"
})
}),
archived: boolean("archived").notNull().default(false)
});
export const currentFingerprint = pgTable("currentFingerprint", {
fingerprintId: serial("id").primaryKey(),
olmId: text("olmId")
.references(() => olms.olmId, { onDelete: "cascade" })
.notNull(),
firstSeen: integer("firstSeen").notNull(),
lastSeen: integer("lastSeen").notNull(),
lastCollectedAt: integer("lastCollectedAt").notNull(),
username: text("username"),
hostname: text("hostname"),
platform: text("platform"),
osVersion: text("osVersion"),
kernelVersion: text("kernelVersion"),
arch: text("arch"),
deviceModel: text("deviceModel"),
serialNumber: text("serialNumber"),
platformFingerprint: varchar("platformFingerprint"),
// Platform-agnostic checks
biometricsEnabled: boolean("biometricsEnabled").notNull().default(false),
diskEncrypted: boolean("diskEncrypted").notNull().default(false),
firewallEnabled: boolean("firewallEnabled").notNull().default(false),
autoUpdatesEnabled: boolean("autoUpdatesEnabled").notNull().default(false),
tpmAvailable: boolean("tpmAvailable").notNull().default(false),
// Windows-specific posture check information
windowsAntivirusEnabled: boolean("windowsAntivirusEnabled")
.notNull()
.default(false),
// macOS-specific posture check information
macosSipEnabled: boolean("macosSipEnabled").notNull().default(false),
macosGatekeeperEnabled: boolean("macosGatekeeperEnabled")
.notNull()
.default(false),
macosFirewallStealthMode: boolean("macosFirewallStealthMode")
.notNull()
.default(false),
// Linux-specific posture check information
linuxAppArmorEnabled: boolean("linuxAppArmorEnabled")
.notNull()
.default(false),
linuxSELinuxEnabled: boolean("linuxSELinuxEnabled").notNull().default(false)
});
export const fingerprintSnapshots = pgTable("fingerprintSnapshots", {
snapshotId: serial("id").primaryKey(),
fingerprintId: integer("fingerprintId").references(
() => currentFingerprint.fingerprintId,
{
onDelete: "set null"
}
),
username: text("username"),
hostname: text("hostname"),
platform: text("platform"),
osVersion: text("osVersion"),
kernelVersion: text("kernelVersion"),
arch: text("arch"),
deviceModel: text("deviceModel"),
serialNumber: text("serialNumber"),
platformFingerprint: varchar("platformFingerprint"),
// Platform-agnostic checks
biometricsEnabled: boolean("biometricsEnabled").notNull().default(false),
diskEncrypted: boolean("diskEncrypted").notNull().default(false),
firewallEnabled: boolean("firewallEnabled").notNull().default(false),
autoUpdatesEnabled: boolean("autoUpdatesEnabled").notNull().default(false),
tpmAvailable: boolean("tpmAvailable").notNull().default(false),
// Windows-specific posture check information
windowsAntivirusEnabled: boolean("windowsAntivirusEnabled")
.notNull()
.default(false),
// macOS-specific posture check information
macosSipEnabled: boolean("macosSipEnabled").notNull().default(false),
macosGatekeeperEnabled: boolean("macosGatekeeperEnabled")
.notNull()
.default(false),
macosFirewallStealthMode: boolean("macosFirewallStealthMode")
.notNull()
.default(false),
// Linux-specific posture check information
linuxAppArmorEnabled: boolean("linuxAppArmorEnabled")
.notNull()
.default(false),
linuxSELinuxEnabled: boolean("linuxSELinuxEnabled")
.notNull()
.default(false),
hash: text("hash").notNull(),
collectedAt: integer("collectedAt").notNull()
});
export const olmSessions = pgTable("clientSession", {
@@ -844,6 +1031,16 @@ export const deviceWebAuthCodes = pgTable("deviceWebAuthCodes", {
})
});
export const roundTripMessageTracker = pgTable("roundTripMessageTracker", {
messageId: serial("messageId").primaryKey(),
wsClientId: varchar("clientId"),
messageType: varchar("messageType"),
sentAt: bigint("sentAt", { mode: "number" }).notNull(),
receivedAt: bigint("receivedAt", { mode: "number" }),
error: text("error"),
complete: boolean("complete").notNull().default(false)
});
export type Org = InferSelectModel<typeof orgs>;
export type User = InferSelectModel<typeof users>;
export type Site = InferSelectModel<typeof sites>;
@@ -867,18 +1064,23 @@ export type UserSite = InferSelectModel<typeof userSites>;
export type RoleResource = InferSelectModel<typeof roleResources>;
export type UserResource = InferSelectModel<typeof userResources>;
export type UserInvite = InferSelectModel<typeof userInvites>;
export type UserInviteRole = InferSelectModel<typeof userInviteRoles>;
export type UserOrg = InferSelectModel<typeof userOrgs>;
export type UserOrgRole = InferSelectModel<typeof userOrgRoles>;
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>;
export type VersionMigration = InferSelectModel<typeof versionMigrations>;
export type ResourceRule = InferSelectModel<typeof resourceRules>;
export type Domain = InferSelectModel<typeof domains>;
export type DnsRecord = InferSelectModel<typeof dnsRecords>;
export type SupporterKey = InferSelectModel<typeof supporterKey>;
export type Idp = InferSelectModel<typeof idp>;
export type ApiKey = InferSelectModel<typeof apiKeys>;
@@ -902,3 +1104,6 @@ export type SecurityKey = InferSelectModel<typeof securityKeys>;
export type WebauthnChallenge = InferSelectModel<typeof webauthnChallenge>;
export type DeviceWebAuthCode = InferSelectModel<typeof deviceWebAuthCodes>;
export type RequestAuditLog = InferSelectModel<typeof requestAuditLog>;
export type RoundTripMessageTracker = InferSelectModel<
typeof roundTripMessageTracker
>;
+27 -23
View File
@@ -1,5 +1,11 @@
import {
db, loginPage, LoginPage, loginPageOrg, Org, orgs,
db,
loginPage,
LoginPage,
loginPageOrg,
Org,
orgs,
roles
} from "@server/db";
import {
Resource,
@@ -14,20 +20,19 @@ import {
resources,
roleResources,
sessions,
userOrgs,
userResources,
users,
ResourceHeaderAuthExtendedCompatibility,
resourceHeaderAuthExtendedCompatibility
} from "@server/db";
import { and, eq } from "drizzle-orm";
import { and, eq, inArray } from "drizzle-orm";
export type ResourceWithAuth = {
resource: Resource | null;
pincode: ResourcePincode | null;
password: ResourcePassword | null;
headerAuth: ResourceHeaderAuth | null;
headerAuthExtendedCompatibility: ResourceHeaderAuthExtendedCompatibility | null
headerAuthExtendedCompatibility: ResourceHeaderAuthExtendedCompatibility | null;
org: Org;
};
@@ -59,12 +64,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 +82,8 @@ export async function getResourceByDomain(
pincode: result.resourcePincode,
password: result.resourcePassword,
headerAuth: result.resourceHeaderAuth,
headerAuthExtendedCompatibility: result.resourceHeaderAuthExtendedCompatibility,
headerAuthExtendedCompatibility:
result.resourceHeaderAuthExtendedCompatibility,
org: result.orgs
};
}
@@ -105,16 +111,15 @@ export async function getUserSessionWithUser(
}
/**
* Get user organization role
* Get role name by role ID (for display).
*/
export async function getUserOrgRole(userId: string, orgId: string) {
const userOrgRole = await db
.select()
.from(userOrgs)
.where(and(eq(userOrgs.userId, userId), eq(userOrgs.orgId, orgId)))
export async function getRoleName(roleId: number): Promise<string | null> {
const [row] = await db
.select({ name: roles.name })
.from(roles)
.where(eq(roles.roleId, roleId))
.limit(1);
return userOrgRole.length > 0 ? userOrgRole[0] : null;
return row?.name ?? null;
}
/**
@@ -122,7 +127,7 @@ export async function getUserOrgRole(userId: string, orgId: string) {
*/
export async function getRoleResourceAccess(
resourceId: number,
roleId: number
roleIds: number[]
) {
const roleResourceAccess = await db
.select()
@@ -130,12 +135,11 @@ export async function getRoleResourceAccess(
.where(
and(
eq(roleResources.resourceId, resourceId),
eq(roleResources.roleId, roleId)
inArray(roleResources.roleId, roleIds)
)
)
.limit(1);
);
return roleResourceAccess.length > 0 ? roleResourceAccess[0] : null;
return roleResourceAccess.length > 0 ? roleResourceAccess : null;
}
/**
+196
View File
@@ -0,0 +1,196 @@
// Regions of the World
// as of 2025-10-25
//
// Adapted according to the United Nations Geoscheme
// see https://www.unicode.org/cldr/charts/48/supplemental/territory_containment_un_m_49.html
// see https://unstats.un.org/unsd/methodology/m49
export const REGIONS = [
{
name: "regionAfrica",
id: "002",
includes: [
{
name: "regionNorthernAfrica",
id: "015",
countries: ["DZ", "EG", "LY", "MA", "SD", "TN", "EH"]
},
{
name: "regionEasternAfrica",
id: "014",
countries: ["IO", "BI", "KM", "DJ", "ER", "ET", "TF", "KE", "MG", "MW", "MU", "YT", "MZ", "RE", "RW", "SC", "SO", "SS", "UG", "ZM", "ZW"]
},
{
name: "regionMiddleAfrica",
id: "017",
countries: ["AO", "CM", "CF", "TD", "CG", "CD", "GQ", "GA", "ST"]
},
{
name: "regionSouthernAfrica",
id: "018",
countries: ["BW", "SZ", "LS", "NA", "ZA"]
},
{
name: "regionWesternAfrica",
id: "011",
countries: ["BJ", "BF", "CV", "CI", "GM", "GH", "GN", "GW", "LR", "ML", "MR", "NE", "NG", "SH", "SN", "SL", "TG"]
}
]
},
{
name: "regionAmericas",
id: "019",
includes: [
{
name: "regionCaribbean",
id: "029",
countries: ["AI", "AG", "AW", "BS", "BB", "BQ", "VG", "KY", "CU", "CW", "DM", "DO", "GD", "GP", "HT", "JM", "MQ", "MS", "PR", "BL", "KN", "LC", "MF", "VC", "SX", "TT", "TC", "VI"]
},
{
name: "regionCentralAmerica",
id: "013",
countries: ["BZ", "CR", "SV", "GT", "HN", "MX", "NI", "PA"]
},
{
name: "regionSouthAmerica",
id: "005",
countries: ["AR", "BO", "BV", "BR", "CL", "CO", "EC", "FK", "GF", "GY", "PY", "PE", "GS", "SR", "UY", "VE"]
},
{
name: "regionNorthernAmerica",
id: "021",
countries: ["BM", "CA", "GL", "PM", "US"]
}
]
},
{
name: "regionAsia",
id: "142",
includes: [
{
name: "regionCentralAsia",
id: "143",
countries: ["KZ", "KG", "TJ", "TM", "UZ"]
},
{
name: "regionEasternAsia",
id: "030",
countries: ["CN", "HK", "MO", "KP", "JP", "MN", "KR"]
},
{
name: "regionSouthEasternAsia",
id: "035",
countries: ["BN", "KH", "ID", "LA", "MY", "MM", "PH", "SG", "TH", "TL", "VN"]
},
{
name: "regionSouthernAsia",
id: "034",
countries: ["AF", "BD", "BT", "IN", "IR", "MV", "NP", "PK", "LK"]
},
{
name: "regionWesternAsia",
id: "145",
countries: ["AM", "AZ", "BH", "CY", "GE", "IQ", "IL", "JO", "KW", "LB", "OM", "QA", "SA", "PS", "SY", "TR", "AE", "YE"]
}
]
},
{
name: "regionEurope",
id: "150",
includes: [
{
name: "regionEasternEurope",
id: "151",
countries: ["BY", "BG", "CZ", "HU", "PL", "MD", "RO", "RU", "SK", "UA"]
},
{
name: "regionNorthernEurope",
id: "154",
countries: ["AX", "DK", "EE", "FO", "FI", "GG", "IS", "IE", "IM", "JE", "LV", "LT", "NO", "SJ", "SE", "GB"]
},
{
name: "regionSouthernEurope",
id: "039",
countries: ["AL", "AD", "BA", "HR", "GI", "GR", "VA", "IT", "MT", "ME", "MK", "PT", "SM", "RS", "SI", "ES"]
},
{
name: "regionWesternEurope",
id: "155",
countries: ["AT", "BE", "FR", "DE", "LI", "LU", "MC", "NL", "CH"]
}
]
},
{
name: "regionOceania",
id: "009",
includes: [
{
name: "regionAustraliaAndNewZealand",
id: "053",
countries: ["AU", "CX", "CC", "HM", "NZ", "NF"]
},
{
name: "regionMelanesia",
id: "054",
countries: ["FJ", "NC", "PG", "SB", "VU"]
},
{
name: "regionMicronesia",
id: "057",
countries: ["GU", "KI", "MH", "FM", "NR", "MP", "PW", "UM"]
},
{
name: "regionPolynesia",
id: "061",
countries: ["AS", "CK", "PF", "NU", "PN", "WS", "TK", "TO", "TV", "WF"]
}
]
}
];
type Subregion = {
name: string;
id: string;
countries: string[];
};
type Region = {
name: string;
id: string;
includes: Subregion[];
};
export function getRegionNameById(regionId: string): string | undefined {
// Check top-level regions
const region = REGIONS.find((r) => r.id === regionId);
if (region) {
return region.name;
}
// Check subregions
for (const region of REGIONS) {
for (const subregion of region.includes) {
if (subregion.id === regionId) {
return subregion.name;
}
}
}
return undefined;
}
export function isValidRegionId(regionId: string): boolean {
// Check top-level regions
if (REGIONS.find((r) => r.id === regionId)) {
return true;
}
// Check subregions
for (const region of REGIONS) {
if (region.includes.find((s) => s.id === regionId)) {
return true;
}
}
return false;
}
+2 -1
View File
@@ -23,7 +23,8 @@ export default db;
export const primaryDb = db;
export type Transaction = Parameters<
Parameters<(typeof db)["transaction"]>[0]
>[0];
>[0];
export const DB_TYPE: "pg" | "sqlite" = "sqlite";
function checkFileExists(filePath: string): boolean {
try {
+3
View File
@@ -1,3 +1,6 @@
export * from "./driver";
export * from "./logsDriver";
export * from "./safeRead";
export * from "./schema/schema";
export * from "./schema/privateSchema";
export * from "./migrate";
+7
View File
@@ -0,0 +1,7 @@
import { db as mainDb } from "./driver";
// SQLite doesn't support separate databases for logs in the same way as Postgres
// Always use the main database connection for SQLite
export const logsDb = mainDb;
export default logsDb;
export const primaryLogsDb = logsDb;
+1 -3
View File
@@ -4,7 +4,7 @@ import path from "path";
const migrationsFolder = path.join("server/migrations");
const runMigrations = async () => {
export const runMigrations = async () => {
console.log("Running migrations...");
try {
migrate(db as any, {
@@ -16,5 +16,3 @@ const runMigrations = async () => {
process.exit(1);
}
};
runMigrations();
+11
View File
@@ -0,0 +1,11 @@
import { db } from "./driver";
/**
* Runs a read query. For SQLite there is no replica/primary distinction,
* so the query is executed once against the database.
*/
export async function safeRead<T>(
query: (d: typeof db) => Promise<T>
): Promise<T> {
return query(db);
}
+181 -4
View File
@@ -2,11 +2,22 @@ import { InferSelectModel } from "drizzle-orm";
import {
index,
integer,
primaryKey,
real,
sqliteTable,
text
text,
uniqueIndex
} from "drizzle-orm/sqlite-core";
import { domains, exitNodes, orgs, sessions, users } from "./schema";
import {
clients,
domains,
exitNodes,
orgs,
sessions,
siteResources,
sites,
users
} from "./schema";
export const certificates = sqliteTable("certificates", {
certId: integer("certId").primaryKey({ autoIncrement: true }),
@@ -70,13 +81,16 @@ export const subscriptions = sqliteTable("subscriptions", {
canceledAt: integer("canceledAt"),
createdAt: integer("createdAt").notNull(),
updatedAt: integer("updatedAt"),
billingCycleAnchor: integer("billingCycleAnchor")
version: integer("version"),
billingCycleAnchor: integer("billingCycleAnchor"),
type: text("type") // tier1, tier2, tier3, or license
});
export const subscriptionItems = sqliteTable("subscriptionItems", {
subscriptionItemId: integer("subscriptionItemId").primaryKey({
autoIncrement: true
}),
stripeSubscriptionItemId: text("stripeSubscriptionItemId"),
subscriptionId: text("subscriptionId")
.notNull()
.references(() => subscriptions.subscriptionId, {
@@ -84,6 +98,7 @@ export const subscriptionItems = sqliteTable("subscriptionItems", {
}),
planId: text("planId").notNull(),
priceId: text("priceId"),
featureId: text("featureId"),
meterId: text("meterId"),
unitAmount: real("unitAmount"),
tiers: text("tiers"),
@@ -126,6 +141,7 @@ export const limits = sqliteTable("limits", {
})
.notNull(),
value: real("value"),
override: integer("override", { mode: "boolean" }).default(false),
description: text("description")
});
@@ -206,7 +222,7 @@ export const loginPageBranding = sqliteTable("loginPageBranding", {
loginPageBrandingId: integer("loginPageBrandingId").primaryKey({
autoIncrement: true
}),
logoUrl: text("logoUrl").notNull(),
logoUrl: text("logoUrl"),
logoWidth: integer("logoWidth").notNull(),
logoHeight: integer("logoHeight").notNull(),
primaryColor: text("primaryColor"),
@@ -273,6 +289,7 @@ export const accessAuditLog = sqliteTable(
actor: text("actor"),
actorId: text("actorId"),
resourceId: integer("resourceId"),
siteResourceId: integer("siteResourceId"),
ip: text("ip"),
location: text("location"),
type: text("type").notNull(),
@@ -289,6 +306,156 @@ export const accessAuditLog = sqliteTable(
]
);
export const connectionAuditLog = sqliteTable(
"connectionAuditLog",
{
id: integer("id").primaryKey({ autoIncrement: true }),
sessionId: text("sessionId").notNull(),
siteResourceId: integer("siteResourceId").references(
() => siteResources.siteResourceId,
{ onDelete: "cascade" }
),
orgId: text("orgId").references(() => orgs.orgId, {
onDelete: "cascade"
}),
siteId: integer("siteId").references(() => sites.siteId, {
onDelete: "cascade"
}),
clientId: integer("clientId").references(() => clients.clientId, {
onDelete: "cascade"
}),
userId: text("userId").references(() => users.userId, {
onDelete: "cascade"
}),
sourceAddr: text("sourceAddr").notNull(),
destAddr: text("destAddr").notNull(),
protocol: text("protocol").notNull(),
startedAt: integer("startedAt").notNull(),
endedAt: integer("endedAt"),
bytesTx: integer("bytesTx"),
bytesRx: integer("bytesRx")
},
(table) => [
index("idx_accessAuditLog_startedAt").on(table.startedAt),
index("idx_accessAuditLog_org_startedAt").on(
table.orgId,
table.startedAt
),
index("idx_accessAuditLog_siteResourceId").on(table.siteResourceId)
]
);
export const approvals = sqliteTable("approvals", {
approvalId: integer("approvalId").primaryKey({ autoIncrement: true }),
timestamp: integer("timestamp").notNull(), // this is EPOCH time in seconds
orgId: text("orgId")
.references(() => orgs.orgId, {
onDelete: "cascade"
})
.notNull(),
clientId: integer("clientId").references(() => clients.clientId, {
onDelete: "cascade"
}), // olms reference user devices clients
userId: text("userId").references(() => users.userId, {
// optionally tied to a user and in this case delete when the user deletes
onDelete: "cascade"
}),
decision: text("decision")
.$type<"approved" | "denied" | "pending">()
.default("pending")
.notNull(),
type: text("type")
.$type<"user_device" /*| 'proxy' // for later */>()
.notNull()
});
export const bannedEmails = sqliteTable("bannedEmails", {
email: text("email").primaryKey()
});
export const bannedIps = sqliteTable("bannedIps", {
ip: text("ip").primaryKey()
});
export const siteProvisioningKeys = sqliteTable("siteProvisioningKeys", {
siteProvisioningKeyId: text("siteProvisioningKeyId").primaryKey(),
name: text("name").notNull(),
siteProvisioningKeyHash: text("siteProvisioningKeyHash").notNull(),
lastChars: text("lastChars").notNull(),
createdAt: text("dateCreated").notNull(),
lastUsed: text("lastUsed"),
maxBatchSize: integer("maxBatchSize"), // null = no limit
numUsed: integer("numUsed").notNull().default(0),
validUntil: text("validUntil"),
approveNewSites: integer("approveNewSites", { mode: "boolean" })
.notNull()
.default(true)
});
export const siteProvisioningKeyOrg = sqliteTable(
"siteProvisioningKeyOrg",
{
siteProvisioningKeyId: text("siteProvisioningKeyId")
.notNull()
.references(() => siteProvisioningKeys.siteProvisioningKeyId, {
onDelete: "cascade"
}),
orgId: text("orgId")
.notNull()
.references(() => orgs.orgId, { onDelete: "cascade" })
},
(table) => [
primaryKey({
columns: [table.siteProvisioningKeyId, table.orgId]
})
]
);
export const eventStreamingDestinations = sqliteTable(
"eventStreamingDestinations",
{
destinationId: integer("destinationId").primaryKey({
autoIncrement: true
}),
orgId: text("orgId")
.notNull()
.references(() => orgs.orgId, { onDelete: "cascade" }),
sendConnectionLogs: integer("sendConnectionLogs", { mode: "boolean" }).notNull().default(false),
sendRequestLogs: integer("sendRequestLogs", { mode: "boolean" }).notNull().default(false),
sendActionLogs: integer("sendActionLogs", { mode: "boolean" }).notNull().default(false),
sendAccessLogs: integer("sendAccessLogs", { mode: "boolean" }).notNull().default(false),
type: text("type").notNull(), // e.g. "http", "kafka", etc.
config: text("config").notNull(), // JSON string with the configuration for the destination
enabled: integer("enabled", { mode: "boolean" })
.notNull()
.default(true),
createdAt: integer("createdAt").notNull(),
updatedAt: integer("updatedAt").notNull()
}
);
export const eventStreamingCursors = sqliteTable(
"eventStreamingCursors",
{
cursorId: integer("cursorId").primaryKey({ autoIncrement: true }),
destinationId: integer("destinationId")
.notNull()
.references(() => eventStreamingDestinations.destinationId, {
onDelete: "cascade"
}),
logType: text("logType").notNull(), // "request" | "action" | "access" | "connection"
lastSentId: integer("lastSentId").notNull().default(0),
lastSentAt: integer("lastSentAt") // epoch milliseconds, null if never sent
},
(table) => [
uniqueIndex("idx_eventStreamingCursors_dest_type").on(
table.destinationId,
table.logType
)
]
);
export type Approval = InferSelectModel<typeof approvals>;
export type Limit = InferSelectModel<typeof limits>;
export type Account = InferSelectModel<typeof account>;
export type Certificate = InferSelectModel<typeof certificates>;
@@ -309,3 +476,13 @@ export type LoginPage = InferSelectModel<typeof loginPage>;
export type LoginPageBranding = InferSelectModel<typeof loginPageBranding>;
export type ActionAuditLog = InferSelectModel<typeof actionAuditLog>;
export type AccessAuditLog = InferSelectModel<typeof accessAuditLog>;
export type ConnectionAuditLog = InferSelectModel<typeof connectionAuditLog>;
export type BannedEmail = InferSelectModel<typeof bannedEmails>;
export type BannedIp = InferSelectModel<typeof bannedIps>;
export type SiteProvisioningKey = InferSelectModel<typeof siteProvisioningKeys>;
export type EventStreamingDestination = InferSelectModel<
typeof eventStreamingDestinations
>;
export type EventStreamingCursor = InferSelectModel<
typeof eventStreamingCursors
>;
+381 -133
View File
@@ -1,33 +1,34 @@
import { randomUUID } from "crypto";
import { InferSelectModel } from "drizzle-orm";
import {
index,
integer,
primaryKey,
sqliteTable,
text,
integer,
index,
uniqueIndex
unique
} from "drizzle-orm/sqlite-core";
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" }),
errorMessage: text("errorMessage")
});
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 +42,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
@@ -52,29 +53,36 @@ export const orgs = sqliteTable("orgs", {
.default(0),
settingsLogRetentionDaysAction: integer("settingsLogRetentionDaysAction") // where 0 = dont keep logs and -1 = keep forever and 9001 = end of the following year
.notNull()
.default(0)
.default(0),
settingsLogRetentionDaysConnection: integer("settingsLogRetentionDaysConnection") // where 0 = dont keep logs and -1 = keep forever and 9001 = end of the following year
.notNull()
.default(0),
sshCaPrivateKey: text("sshCaPrivateKey"), // Encrypted SSH CA private key (PEM format)
sshCaPublicKey: text("sshCaPublicKey"), // SSH CA public key (OpenSSH format)
isBillingOrg: integer("isBillingOrg", { mode: "boolean" }),
billingOrgId: text("billingOrgId")
});
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 +99,8 @@ 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),
lastPing: integer("lastPing"),
// 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 +108,15 @@ 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)
.default(true),
status: text("status").$type<"pending" | "approved">().default("approved")
});
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 +132,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", {
@@ -159,11 +173,11 @@ export const resources = sqliteTable("resources", {
maintenanceTitle: text("maintenanceTitle"),
maintenanceMessage: text("maintenanceMessage"),
maintenanceEstimatedTime: text("maintenanceEstimatedTime"),
postAuthPath: text("postAuthPath")
});
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 +192,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 +206,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"),
@@ -210,12 +224,14 @@ export const targetHealthCheck = sqliteTable("targetHealthCheck", {
}).default(true),
hcMethod: text("hcMethod").default("GET"),
hcStatus: integer("hcStatus"), // http code
hcHealth: text("hcHealth").default("unknown"), // "unknown", "healthy", "unhealthy"
hcHealth: text("hcHealth")
.$type<"unknown" | "healthy" | "unhealthy">()
.default("unknown"), // "unknown", "healthy", "unhealthy"
hcTlsServerName: text("hcTlsServerName")
});
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 +239,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,13 +252,13 @@ 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"
mode: text("mode").$type<"host" | "cidr">().notNull(), // "host" | "cidr" | "port"
protocol: text("protocol"), // only for port mode
proxyPort: integer("proxyPort"), // only for port mode
destinationPort: integer("destinationPort"), // only for port mode
@@ -250,9 +266,15 @@ 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"),
tcpPortRangeString: text("tcpPortRangeString").notNull().default("*"),
udpPortRangeString: text("udpPortRangeString").notNull().default("*"),
disableIcmp: integer("disableIcmp", { mode: "boolean" })
.notNull()
.default(false),
authDaemonPort: integer("authDaemonPort").default(22123),
authDaemonMode: text("authDaemonMode")
.$type<"site" | "remote">()
.default("site")
});
export const clientSiteResources = sqliteTable("clientSiteResources", {
@@ -292,23 +314,27 @@ 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"})
marketingEmailConsent: integer("marketingEmailConsent", {
mode: "boolean"
}).default(false),
serverAdmin: integer("serverAdmin", { mode: "boolean" })
.notNull()
.default(false),
lastPasswordChange: integer("lastPasswordChange")
lastPasswordChange: integer("lastPasswordChange"),
locale: text("locale")
});
export const securityKeys = sqliteTable("webauthnCredentials", {
@@ -339,7 +365,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,9 +404,14 @@ 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")
lastHolePunch: integer("lastHolePunch"),
archived: integer("archived", { mode: "boolean" }).notNull().default(false),
blocked: integer("blocked", { mode: "boolean" }).notNull().default(false),
approvalState: text("approvalState").$type<
"pending" | "approved" | "denied"
>()
});
export const clientSitesAssociationsCache = sqliteTable(
@@ -392,6 +423,9 @@ export const clientSitesAssociationsCache = sqliteTable(
isRelayed: integer("isRelayed", { mode: "boolean" })
.notNull()
.default(false),
isJitMode: integer("isJitMode", { mode: "boolean" })
.notNull()
.default(false),
endpoint: text("endpoint"),
publicKey: text("publicKey") // this will act as the session's public key for hole punching so we can track when it changes
}
@@ -420,14 +454,167 @@ export const olms = sqliteTable("olms", {
userId: text("userId").references(() => users.userId, {
// optionally tied to a user and in this case delete when the user deletes
onDelete: "cascade"
}),
archived: integer("archived", { mode: "boolean" }).notNull().default(false)
});
export const currentFingerprint = sqliteTable("currentFingerprint", {
fingerprintId: integer("id").primaryKey({ autoIncrement: true }),
olmId: text("olmId")
.references(() => olms.olmId, { onDelete: "cascade" })
.notNull(),
firstSeen: integer("firstSeen").notNull(),
lastSeen: integer("lastSeen").notNull(),
lastCollectedAt: integer("lastCollectedAt").notNull(),
username: text("username"),
hostname: text("hostname"),
platform: text("platform"),
osVersion: text("osVersion"),
kernelVersion: text("kernelVersion"),
arch: text("arch"),
deviceModel: text("deviceModel"),
serialNumber: text("serialNumber"),
platformFingerprint: text("platformFingerprint"),
// Platform-agnostic checks
biometricsEnabled: integer("biometricsEnabled", { mode: "boolean" })
.notNull()
.default(false),
diskEncrypted: integer("diskEncrypted", { mode: "boolean" })
.notNull()
.default(false),
firewallEnabled: integer("firewallEnabled", { mode: "boolean" })
.notNull()
.default(false),
autoUpdatesEnabled: integer("autoUpdatesEnabled", { mode: "boolean" })
.notNull()
.default(false),
tpmAvailable: integer("tpmAvailable", { mode: "boolean" })
.notNull()
.default(false),
// Windows-specific posture check information
windowsAntivirusEnabled: integer("windowsAntivirusEnabled", {
mode: "boolean"
})
.notNull()
.default(false),
// macOS-specific posture check information
macosSipEnabled: integer("macosSipEnabled", { mode: "boolean" })
.notNull()
.default(false),
macosGatekeeperEnabled: integer("macosGatekeeperEnabled", {
mode: "boolean"
})
.notNull()
.default(false),
macosFirewallStealthMode: integer("macosFirewallStealthMode", {
mode: "boolean"
})
.notNull()
.default(false),
// Linux-specific posture check information
linuxAppArmorEnabled: integer("linuxAppArmorEnabled", { mode: "boolean" })
.notNull()
.default(false),
linuxSELinuxEnabled: integer("linuxSELinuxEnabled", {
mode: "boolean"
})
.notNull()
.default(false)
});
export const fingerprintSnapshots = sqliteTable("fingerprintSnapshots", {
snapshotId: integer("id").primaryKey({ autoIncrement: true }),
fingerprintId: integer("fingerprintId").references(
() => currentFingerprint.fingerprintId,
{
onDelete: "set null"
}
),
username: text("username"),
hostname: text("hostname"),
platform: text("platform"),
osVersion: text("osVersion"),
kernelVersion: text("kernelVersion"),
arch: text("arch"),
deviceModel: text("deviceModel"),
serialNumber: text("serialNumber"),
platformFingerprint: text("platformFingerprint"),
// Platform-agnostic checks
biometricsEnabled: integer("biometricsEnabled", { mode: "boolean" })
.notNull()
.default(false),
diskEncrypted: integer("diskEncrypted", { mode: "boolean" })
.notNull()
.default(false),
firewallEnabled: integer("firewallEnabled", { mode: "boolean" })
.notNull()
.default(false),
autoUpdatesEnabled: integer("autoUpdatesEnabled", { mode: "boolean" })
.notNull()
.default(false),
tpmAvailable: integer("tpmAvailable", { mode: "boolean" })
.notNull()
.default(false),
// Windows-specific posture check information
windowsAntivirusEnabled: integer("windowsAntivirusEnabled", {
mode: "boolean"
})
.notNull()
.default(false),
// macOS-specific posture check information
macosSipEnabled: integer("macosSipEnabled", { mode: "boolean" })
.notNull()
.default(false),
macosGatekeeperEnabled: integer("macosGatekeeperEnabled", {
mode: "boolean"
})
.notNull()
.default(false),
macosFirewallStealthMode: integer("macosFirewallStealthMode", {
mode: "boolean"
})
.notNull()
.default(false),
// Linux-specific posture check information
linuxAppArmorEnabled: integer("linuxAppArmorEnabled", { mode: "boolean" })
.notNull()
.default(false),
linuxSELinuxEnabled: integer("linuxSELinuxEnabled", {
mode: "boolean"
})
.notNull()
.default(false),
hash: text("hash").notNull(),
collectedAt: integer("collectedAt").notNull()
});
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 +622,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 +634,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,44 +642,42 @@ 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"
})
.notNull(),
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)
}).default(false),
pamUsername: text("pamUsername") // cleaned username for ssh and such
});
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,115 +689,150 @@ 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")
description: text("description"),
requireDeviceApproval: integer("requireDeviceApproval", {
mode: "boolean"
}).default(false),
sshSudoMode: text("sshSudoMode").default("none"), // "none" | "full" | "commands"
sshSudoCommands: text("sshSudoCommands").default("[]"),
sshCreateHomeDir: integer("sshCreateHomeDir", { mode: "boolean" }).default(
true
),
sshUnixGroups: text("sshUnixGroups").default("[]")
});
export const userOrgRoles = sqliteTable(
"userOrgRoles",
{
userId: text("userId")
.notNull()
.references(() => users.userId, { onDelete: "cascade" }),
orgId: text("orgId")
.notNull()
.references(() => orgs.orgId, { onDelete: "cascade" }),
roleId: integer("roleId")
.notNull()
.references(() => roles.roleId, { onDelete: "cascade" })
},
(t) => [unique().on(t.userId, t.orgId, t.roleId)]
);
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"})
tokenHash: text("token").notNull()
});
export const userInviteRoles = sqliteTable(
"userInviteRoles",
{
inviteId: text("inviteId")
.notNull()
.references(() => userInvites.inviteId, { onDelete: "cascade" }),
roleId: integer("roleId")
.notNull()
.references(() => roles.roleId, { onDelete: "cascade" })
},
(t) => [primaryKey({ columns: [t.inviteId, t.roleId] })]
);
export const resourcePincode = sqliteTable("resourcePincode", {
pincodeId: integer("pincodeId").primaryKey({
autoIncrement: true
}),
resourceId: integer("resourceId")
.notNull()
.references(() => resources.resourceId, {onDelete: "cascade"}),
.references(() => resources.resourceId, { onDelete: "cascade" }),
pincodeHash: text("pincodeHash").notNull(),
digitLength: integer("digitLength").notNull()
});
@@ -623,7 +843,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 +853,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 +897,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 +935,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 +948,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 +960,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 +972,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"),
@@ -761,7 +991,8 @@ export const idp = sqliteTable("idp", {
mode: "boolean"
})
.notNull()
.default(false)
.default(false),
tags: text("tags")
});
// Identity Provider OAuth Configuration
@@ -772,7 +1003,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 +1031,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 +1057,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 +1078,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 +1107,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),
@@ -902,6 +1133,16 @@ export const deviceWebAuthCodes = sqliteTable("deviceWebAuthCodes", {
})
});
export const roundTripMessageTracker = sqliteTable("roundTripMessageTracker", {
messageId: integer("messageId").primaryKey({ autoIncrement: true }),
wsClientId: text("clientId"),
messageType: text("messageType"),
sentAt: integer("sentAt").notNull(),
receivedAt: integer("receivedAt"),
error: text("error"),
complete: integer("complete", { mode: "boolean" }).notNull().default(false)
});
export type Org = InferSelectModel<typeof orgs>;
export type User = InferSelectModel<typeof users>;
export type Site = InferSelectModel<typeof sites>;
@@ -927,12 +1168,16 @@ export type UserSite = InferSelectModel<typeof userSites>;
export type RoleResource = InferSelectModel<typeof roleResources>;
export type UserResource = InferSelectModel<typeof userResources>;
export type UserInvite = InferSelectModel<typeof userInvites>;
export type UserInviteRole = InferSelectModel<typeof userInviteRoles>;
export type UserOrg = InferSelectModel<typeof userOrgs>;
export type UserOrgRole = InferSelectModel<typeof userOrgRoles>;
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>;
@@ -961,3 +1206,6 @@ export type SecurityKey = InferSelectModel<typeof securityKeys>;
export type WebauthnChallenge = InferSelectModel<typeof webauthnChallenge>;
export type RequestAuditLog = InferSelectModel<typeof requestAuditLog>;
export type DeviceWebAuthCode = InferSelectModel<typeof deviceWebAuthCodes>;
export type RoundTripMessageTracker = InferSelectModel<
typeof roundTripMessageTracker
>;
@@ -0,0 +1,118 @@
import React from "react";
import { Body, Head, Html, Preview, Tailwind } from "@react-email/components";
import { themeColors } from "./lib/theme";
import {
EmailContainer,
EmailFooter,
EmailGreeting,
EmailHeading,
EmailInfoSection,
EmailLetterHead,
EmailSection,
EmailSignature,
EmailText
} from "./components/Email";
import CopyCodeBox from "./components/CopyCodeBox";
import ButtonLink from "./components/ButtonLink";
type EnterpriseEditionKeyGeneratedProps = {
keyValue: string;
personalUseOnly: boolean;
users: number;
sites: number;
modifySubscriptionLink?: string;
};
export const EnterpriseEditionKeyGenerated = ({
keyValue,
personalUseOnly,
users,
sites,
modifySubscriptionLink
}: EnterpriseEditionKeyGeneratedProps) => {
const previewText = personalUseOnly
? "Your Enterprise Edition key for personal use is ready"
: "Thank you for your purchase — your Enterprise Edition key is ready";
return (
<Html>
<Head />
<Preview>{previewText}</Preview>
<Tailwind config={themeColors}>
<Body className="font-sans bg-gray-50">
<EmailContainer>
<EmailLetterHead />
<EmailGreeting>Hi there,</EmailGreeting>
{personalUseOnly ? (
<EmailText>
Your Enterprise Edition license key has been
generated. Qualifying users can use the
Enterprise Edition for free for{" "}
<strong>personal use only</strong>.
</EmailText>
) : (
<>
<EmailText>
Thank you for your purchase. Your Enterprise
Edition license key is ready. Below are the
terms of your license.
</EmailText>
<EmailInfoSection
title="License details"
items={[
{
label: "Licensed users",
value: users
},
{
label: "Licensed sites",
value: sites
}
]}
/>
{modifySubscriptionLink && (
<EmailSection>
<ButtonLink
href={modifySubscriptionLink}
>
Modify subscription
</ButtonLink>
</EmailSection>
)}
</>
)}
<EmailSection>
<EmailText>Your license key:</EmailText>
<CopyCodeBox
text={keyValue}
hint="Copy this key and use it when activating Enterprise Edition on your Pangolin host."
/>
</EmailSection>
<EmailText>
If you need to purchase additional license keys or
modify your existing license, please reach out to
our support team at{" "}
<a
href="mailto:support@pangolin.net"
className="text-primary font-medium"
>
support@pangolin.net
</a>
.
</EmailText>
<EmailFooter>
<EmailSignature />
</EmailFooter>
</EmailContainer>
</Body>
</Tailwind>
</Html>
);
};
export default EnterpriseEditionKeyGenerated;

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