Compare commits

..

1456 Commits

Author SHA1 Message Date
dependabot[bot]
4319460fe7 Bump the github-actions-dependencies group across 1 directory with 2 updates
Bumps the github-actions-dependencies group with 2 updates in the / directory: [docker/login-action](https://github.com/docker/login-action) and [actions/setup-go](https://github.com/actions/setup-go).


Updates `docker/login-action` from 4.2.0 to 4.4.0
- [Release notes](https://github.com/docker/login-action/releases)
- [Commits](650006c6eb...af1e73f918)

Updates `actions/setup-go` from 6.4.0 to 6.5.0
- [Release notes](https://github.com/actions/setup-go/releases)
- [Commits](4a3601121d...924ae3a1cd)

---
updated-dependencies:
- dependency-name: docker/login-action
  dependency-version: 4.4.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: github-actions-dependencies
- dependency-name: actions/setup-go
  dependency-version: 6.5.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: github-actions-dependencies
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-06 01:33:42 +00:00
Owen Schwartz
d4c52bbf2f Merge pull request #3396 from fosrl/dev
1.19.4-s.5
2026-07-03 16:15:18 -04:00
Owen
390c822bb4 Fix issue with overlapping site resources not sending 2026-07-03 16:13:50 -04:00
Owen
2bfc1901a6 Dont check the subnet because we dont use it 2026-07-03 14:38:01 -04:00
Owen
5da186b528 Quiet warning messages 2026-07-03 11:58:00 -04:00
Owen
600a96c13b Update rate limit to 10 2026-07-03 11:52:59 -04:00
Owen
e4e0da3723 Add not exists check 2026-07-03 11:24:53 -04:00
Owen Schwartz
87f50bf0cc Merge pull request #3389 from fosrl/dev
1.19.4-s.3
2026-07-03 10:29:15 -04:00
Owen
440ebfe08e Clarify error messages 2026-07-03 10:26:13 -04:00
Owen
b399d2a291 Add some retry and database confict mitigation 2026-07-03 10:23:32 -04:00
Owen Schwartz
2c66da1b19 Merge pull request #3386 from v1rusnl/main
Upgrade Traefik image to version 3.7
2026-07-03 10:18:44 -04:00
Owen
1b1fba60f1 Make sure to retry rebuilds 2026-07-03 09:02:08 -04:00
v1rusnl
ab19955502 Upgrade Traefik image to version 3.7 2026-07-03 08:22:13 +02:00
v1rusnl
1db9dcec81 Update Traefik image version to v3.7 2026-07-03 08:21:12 +02:00
Owen
b93d26f09f Fix reading from replicas 2026-07-02 21:46:53 -04:00
Owen
fc54ad49b5 Fix trial showing 2026-07-02 21:46:44 -04:00
Owen
f87e136f6b Unique subnets for exit nodes 2026-07-02 20:54:59 -04:00
Owen
1bf3d2cdd6 Add back the sync with semver 2026-07-02 12:10:20 -04:00
Owen
5fc5a3ebca Adjust spacing 2026-07-02 11:49:49 -04:00
Owen Schwartz
49c2d3163e Merge pull request #3381 from fosrl/dev
dev
2026-07-02 10:56:39 -04:00
Owen
e40f325703 Add new limits to the billing page 2026-07-02 10:55:46 -04:00
Owen
8f377a4fb2 Dont run on cloud 2026-07-01 22:07:33 -04:00
Owen Schwartz
45b9e13a13 Merge pull request #3378 from fosrl/dev
1.19.4-s.1
2026-07-01 21:48:01 -04:00
Owen Schwartz
3699f8f9cb Merge pull request #3380 from fosrl/resource-launcher
improve pagination
2026-07-01 21:47:14 -04:00
Owen
5a2388a1e6 Fix imports 2026-07-01 21:43:51 -04:00
Owen
86e6ebc8af Comment out sync again for now 2026-07-01 21:40:08 -04:00
miloschwartz
005f050a81 improve pagination 2026-07-01 21:36:23 -04:00
Owen Schwartz
c82678852e Merge pull request #3377 from fosrl/crowdin_dev
New Crowdin updates
2026-07-01 21:30:26 -04:00
Owen Schwartz
2e3ab10f5e New translations en-us.json (Norwegian Bokmal)
[ci skip]
2026-07-01 21:28:16 -04:00
Owen Schwartz
4985ed02d3 New translations en-us.json (Chinese Simplified)
[ci skip]
2026-07-01 21:28:14 -04:00
Owen Schwartz
a2fea7f714 New translations en-us.json (Turkish)
[ci skip]
2026-07-01 21:28:12 -04:00
Owen Schwartz
ddba2aff21 New translations en-us.json (Russian)
[ci skip]
2026-07-01 21:28:11 -04:00
Owen Schwartz
da9e668fb8 New translations en-us.json (Portuguese)
[ci skip]
2026-07-01 21:28:09 -04:00
Owen Schwartz
9c2d14d8c6 New translations en-us.json (Polish)
[ci skip]
2026-07-01 21:28:07 -04:00
Owen Schwartz
c383e74df4 New translations en-us.json (Dutch)
[ci skip]
2026-07-01 21:28:05 -04:00
Owen Schwartz
40101f8bb0 New translations en-us.json (Korean)
[ci skip]
2026-07-01 21:28:03 -04:00
Owen Schwartz
b20ed9efa9 New translations en-us.json (Italian)
[ci skip]
2026-07-01 21:28:02 -04:00
Owen Schwartz
56266e3b62 New translations en-us.json (German)
[ci skip]
2026-07-01 21:28:00 -04:00
Owen Schwartz
47eff8c948 New translations en-us.json (Danish)
[ci skip]
2026-07-01 21:27:58 -04:00
Owen Schwartz
15f36096ef New translations en-us.json (Czech)
[ci skip]
2026-07-01 21:27:57 -04:00
Owen Schwartz
064252586d New translations en-us.json (Bulgarian)
[ci skip]
2026-07-01 21:27:55 -04:00
Owen Schwartz
6181d46a1e New translations en-us.json (Spanish)
[ci skip]
2026-07-01 21:27:53 -04:00
Owen Schwartz
9747731668 New translations en-us.json (French)
[ci skip]
2026-07-01 21:27:51 -04:00
Owen Schwartz
2a478eef6f Merge pull request #3375 from fosrl/resource-launcher
Resource launcher
2026-07-01 21:19:19 -04:00
Owen
4ab101f8a9 Fix lock 2026-07-01 21:13:40 -04:00
miloschwartz
e35878ee55 add caching 2026-07-01 19:59:38 -04:00
Owen
807613f28c Move rate limit up 2026-07-01 17:30:07 -04:00
miloschwartz
663244fa3a support labels in list alises 2026-07-01 16:17:27 -04:00
Owen
bcc128aeb6 Fix ping function 2026-07-01 15:47:24 -04:00
Owen
ba33cb9895 Increment config version for this 2026-07-01 15:44:08 -04:00
Owen
69e7fedcfc Add the remote exit node info 2026-07-01 15:28:26 -04:00
miloschwartz
023110b341 update en-us 2026-07-01 15:18:49 -04:00
miloschwartz
7fb95e1726 restore script 2026-07-01 15:15:43 -04:00
miloschwartz
db0a7cc1ce fix toolbar responsiveness and disable side panel 2026-07-01 15:14:29 -04:00
Owen
61fc2e5ea7 Fix restartSite import 2026-07-01 14:48:36 -04:00
miloschwartz
0871a211ec open side panel on click 2026-07-01 14:30:43 -04:00
miloschwartz
5a1d5cb66e redirect to settings if org admin 2026-07-01 14:06:33 -04:00
miloschwartz
5a7ca5b542 add refresh button 2026-07-01 12:38:45 -04:00
miloschwartz
87e1a509ce show sites and labels for non admins 2026-07-01 12:17:36 -04:00
miloschwartz
75f481bc3d improve search bar debounce behavior 2026-07-01 11:53:11 -04:00
miloschwartz
97cdb2eb5a use proper label filter selector 2026-07-01 11:46:20 -04:00
miloschwartz
297fd2caf3 standardize permissions in api 2026-07-01 11:26:14 -04:00
miloschwartz
22dd4220fe fix ordering of buttons 2026-07-01 11:09:26 -04:00
Owen
108cb6216c Add migration to fix policy delete issues
Ref #3257
2026-07-01 10:39:41 -04:00
Owen
e3ef592778 Adjust language 2026-07-01 10:39:41 -04:00
miloschwartz
3c37e10638 add mobile overflow menu 2026-07-01 10:11:17 -04:00
miloschwartz
561f75b6b1 improve responsiveness 2026-07-01 10:03:06 -04:00
Owen Schwartz
e98bcb83ac Merge pull request #3371 from jaisinha77777/fix/oss-listexitnodes-siteid-signature
Fix OSS build break: add siteId param to OSS listExitNodes
2026-07-01 08:44:12 -04:00
Owen Schwartz
80284863bb Merge pull request #3369 from jaisinha77777/fix/private-resources-delete-error-toast
Fix broken toast on private resource delete failure
2026-07-01 08:43:50 -04:00
miloschwartz
bc759c5c9e improve mobile responsiveness 2026-06-30 22:11:25 -04:00
miloschwartz
f4854a3a74 include exit node endpoint in tcp/udp resources 2026-06-30 21:54:27 -04:00
miloschwartz
376dd465b3 show no site category 2026-06-30 21:49:08 -04:00
jaisinha77777
296439fd67 Fix OSS build break: add siteId param to OSS listExitNodes
listExitNodes is resolved via the #dynamic path alias, which maps to
server/lib in the OSS build and server/private/lib in enterprise/saas.
Commit 9c18936b added a 4th siteId argument to the shared caller
(handleNewtPingRequestMessage) and to the enterprise implementation, but
not to the OSS one, so under the OSS tsconfig the call fails:

  handleNewtPingRequestMessage.ts: error TS2554: Expected 1-3 arguments,
  but got 4.

This breaks 'npx tsc --noEmit' for the OSS build (the CI 'Test with tsc'
step runs it after set:oss). Add siteId?: number to the OSS signature
for parity. It is unused in OSS since that build has no remote exit
nodes to label-filter; accepting it keeps the two #dynamic
implementations interface-compatible.
2026-07-01 07:17:03 +05:30
jaisinha77777
31f675f38c Fix broken toast on private resource delete failure
The delete-error handler in PrivateResourcesTable referenced two i18n
keys that do not exist in the message catalog:

- console.error used "resourceErrorDelete" (the catalog key is
  "resourceErrorDelte"), logging a raw key instead of the message.
- the toast description passed "v" to formatAxiosError, so a failed
  delete showed the user a bogus fallback title.

Point both at the existing "resourceErrorDelte" key, matching the
delete handlers in PublicResourcesTable and ResourcePoliciesTable. No
catalog/translation changes, so nothing changes for Crowdin.
2026-07-01 07:04:54 +05:30
miloschwartz
9f68be2a9b add empty state 2026-06-30 21:18:46 -04:00
miloschwartz
fed4ec42c4 add server side fetching 2026-06-30 21:15:03 -04:00
miloschwartz
f0efa4203b basic functionality 2026-06-30 21:03:19 -04:00
Owen
cfbbdedaf5 rework ui 2026-06-30 17:44:35 -04:00
Owen
686789ee4c Properly translate 2026-06-30 15:37:42 -04:00
Owen
3fda190ff6 Merge branch 'backhaul' into dev 2026-06-30 15:21:11 -04:00
Owen
0033f40f4d Fix typo preventing subscription cancelation 2026-06-30 15:06:35 -04:00
Owen
af95052706 Update schema for tracking valid domains 2026-06-30 13:53:44 -04:00
Owen
9bb2d6cdc8 Prompt for the username on vnc 2026-06-30 12:16:58 -04:00
Owen
29563a13a4 Add indexes on the niceId and orgId 2026-06-30 09:49:53 -04:00
Owen
b41c1f5b27 Add restart button 2026-06-29 21:10:49 -04:00
Owen
e5652cdb8a Dont enable admin routes 2026-06-29 20:45:38 -04:00
Owen
7c2ea153c5 Use regional cache for rate limiting 2026-06-29 18:33:03 -04:00
Owen
ccabddc225 Add logging for access for new public resources 2026-06-29 18:05:29 -04:00
Owen
42d98fa83b Comment back in the sync command 2026-06-29 16:34:10 -04:00
Owen
2f2b7f43c1 Add usage tracking to blueprints 2026-06-29 16:13:12 -04:00
Owen
528bbeca26 Implement usage tracking on resources, clients 2026-06-29 15:39:30 -04:00
Owen
d60c15b0ae Fix typo 2026-06-29 15:24:16 -04:00
Owen
ff89a64453 Rename to limit id 2026-06-29 15:22:35 -04:00
Owen
4718c489d3 Add concurrency guard calculateUserClientsForOrgs 2026-06-29 15:02:41 -04:00
Owen
d5d99a4804 Add org rebuild rate limit 2026-06-29 14:59:05 -04:00
Owen
9c18936be7 Filter the nodes based on the preference labels 2026-06-29 11:40:25 -04:00
Owen
cf07cceb5d Fix bad col in pg 2026-06-29 11:31:34 -04:00
Owen
faee9e6330 Merge branch 'main' into dev 2026-06-29 11:29:12 -04:00
miloschwartz
31725eb3cc display resource type in info card 2026-06-29 10:45:38 -04:00
miloschwartz
04d4e298e8 fix spacing on endpoint field on site 2026-06-29 10:42:25 -04:00
Owen
c9cc9581b1 Add batch update 2026-06-26 23:24:14 -04:00
Owen
eac7c67dcc Send down remote subnets 2026-06-26 18:09:56 -04:00
Owen
633d9031af Add labels input 2026-06-26 18:02:20 -04:00
Owen
05dc558c4a Add basic resources input on the remote node 2026-06-26 18:01:24 -04:00
miloschwartz
7506c0420d properly pass org policy error message in olm register 2026-06-26 17:11:32 -04:00
Owen Schwartz
5572822c4a Merge pull request #3351 from fosrl/copilot/fix-rest-ruleid-changing
fix: preserve rule IDs when saving policy rules via the GUI
2026-06-26 15:05:31 -04:00
Owen
ea3f1c341b Move hashing outside of transaction 2026-06-26 14:40:39 -04:00
Owen
35dffe71cb Make error statement debug 2026-06-26 14:40:31 -04:00
copilot-swe-agent[bot]
5428bf4ed0 fix: preserve rule IDs when saving policy rules through the GUI
The `setResourcePolicyRules` endpoint was deleting all existing rules and
re-inserting them on every save, causing all ruleIDs to change.

Backend: Accept an optional `ruleId` per rule in the request body and
perform an upsert — update existing rules (matched by ruleId), insert
new ones (no ruleId), and delete only rules absent from the incoming list.

Frontend: Include `ruleId` in the rules payload for existing (non-new)
rules so the backend can match and preserve them.
2026-06-26 14:37:34 +00:00
copilot-swe-agent[bot]
9a89579e08 Initial plan 2026-06-26 14:33:35 +00:00
Owen Schwartz
784588cebc Merge pull request #3350 from fosrl/dev
Make sure the rebuild actually executes
2026-06-26 09:28:12 -04:00
Owen
2e628fe0e4 Make sure the rebuild actually executes 2026-06-26 09:26:43 -04:00
Owen Schwartz
7590e8d8a1 Merge pull request #3345 from fosrl/dev
Show utility subnet on org
2026-06-25 13:05:32 -07:00
Owen
053ff1e799 Add utility subnet 2026-06-25 15:39:04 -04:00
Owen Schwartz
c5ffca499e Merge pull request #3330 from fosrl/dev
1.19.3
2026-06-25 11:54:25 -07:00
Owen Schwartz
9ae54f445d Merge pull request #3343 from fosrl/crowdin_dev
New Crowdin updates
2026-06-25 11:45:14 -07:00
Owen Schwartz
8183d19400 New translations en-us.json (Norwegian Bokmal)
[ci skip]
2026-06-25 11:44:44 -07:00
Owen Schwartz
7425daad3f New translations en-us.json (Chinese Simplified)
[ci skip]
2026-06-25 11:44:43 -07:00
Owen Schwartz
39d61a35eb New translations en-us.json (Turkish)
[ci skip]
2026-06-25 11:44:41 -07:00
Owen Schwartz
f3fe11c136 New translations en-us.json (Russian)
[ci skip]
2026-06-25 11:44:39 -07:00
Owen Schwartz
66b1b385a3 New translations en-us.json (Portuguese)
[ci skip]
2026-06-25 11:44:37 -07:00
Owen Schwartz
822a07d48e New translations en-us.json (Polish)
[ci skip]
2026-06-25 11:44:35 -07:00
Owen Schwartz
3c13b1ea15 New translations en-us.json (Dutch)
[ci skip]
2026-06-25 11:44:33 -07:00
Owen Schwartz
fee635b861 New translations en-us.json (Korean)
[ci skip]
2026-06-25 11:44:31 -07:00
Owen Schwartz
7e4dea918a New translations en-us.json (Italian)
[ci skip]
2026-06-25 11:44:29 -07:00
Owen Schwartz
56187d61d5 New translations en-us.json (German)
[ci skip]
2026-06-25 11:44:27 -07:00
Owen Schwartz
36460d4cc0 New translations en-us.json (Danish)
[ci skip]
2026-06-25 11:44:25 -07:00
Owen Schwartz
88a9b92dc3 New translations en-us.json (Czech)
[ci skip]
2026-06-25 11:44:23 -07:00
Owen Schwartz
dd26518d6f New translations en-us.json (Bulgarian)
[ci skip]
2026-06-25 11:44:21 -07:00
Owen Schwartz
60339706bb New translations en-us.json (Spanish)
[ci skip]
2026-06-25 11:44:19 -07:00
Owen Schwartz
4b1b3d3d5b New translations en-us.json (French)
[ci skip]
2026-06-25 11:44:17 -07:00
Owen
cf21bacd9c Merge branch 'main' into dev 2026-06-25 14:30:05 -04:00
Owen Schwartz
80d257b94b Merge pull request #3336 from mr1beast/main
New translations en-us.json (Danish)
2026-06-25 11:24:56 -07:00
Owen Schwartz
e0d0c5dcbf Merge pull request #3331 from Fredkiss3/feat/geoip-tag-in-tables
feat: Show GeoIp country flags in site & rules page
2026-06-25 11:23:42 -07:00
Fred KISSIE
0f02d1bc02 ♻️ remove deprecated ISO CS country code 2026-06-25 18:01:25 +02:00
Owen
f8591f27c5 Fix no data when last data was over 90 days ago 2026-06-25 10:08:37 -04:00
Owen
877985deb3 Spellcheck 2026-06-24 18:36:01 -04:00
Owen
be3877a3ce Rename for clarity 2026-06-24 18:36:01 -04:00
Owen
79de64dc07 Fix removing site not removing peer 2026-06-24 18:36:01 -04:00
miloschwartz
d0defa380a remove split by command and space in role form 2026-06-24 17:52:21 -04:00
miloschwartz
4eba51de72 support delete resources associated with site 2026-06-24 17:45:44 -04:00
mr1beast
a48032adb3 New translations en-us.json (Danish)
New translations en-us.json (Danish)
2026-06-24 21:25:57 +00:00
miloschwartz
6fe4eee336 improve org policy error message responses 2026-06-24 16:32:58 -04:00
Owen
242123b875 Implement non-redis lock 2026-06-24 16:01:05 -04:00
miloschwartz
2b38658ea6 make sidebar notification failures more resilient 2026-06-24 15:55:29 -04:00
miloschwartz
b18a41e4aa adjust translation 2026-06-24 15:55:29 -04:00
Owen
d303fa05cb Comment out the sync 2026-06-24 15:50:54 -04:00
Owen
75b87ffba7 Quiet log message 2026-06-24 15:49:51 -04:00
Owen
62fc2edae9 Add logging and fix removing alias 2026-06-24 15:28:46 -04:00
Owen
80b66cf9b9 Add locks to rebuilds 2026-06-24 14:13:11 -04:00
Owen
034bcbd271 Reorg 2026-06-24 11:54:56 -04:00
Owen
bc63747efe Refactor out transactions and always call rebuild on update 2026-06-23 18:12:51 -04:00
Fred KISSIE
bb7729df00 💄 show geoip flag in policy access rule tab 2026-06-23 23:45:59 +02:00
Owen
2a8ceeec1b Restrict admin role 2026-06-23 17:45:42 -04:00
Owen
91ef0d0153 Show warning about the .local aliases 2026-06-23 17:44:18 -04:00
Fred KISSIE
e104489257 💄 Show GeoIp flag in site details page 2026-06-23 23:28:46 +02:00
Owen
b8101402cd Merge branch 'main' into dev 2026-06-23 17:14:53 -04:00
Owen
7731849a2f Standardize db rebuildClientAssociationsFromClient 2026-06-23 17:14:40 -04:00
Owen
c11d24e10a Standardize db rebuildClientAssociationsFromClient 2026-06-23 17:14:40 -04:00
Owen
a9b7cce49b Improve efficiency of calculateUserClientsForOrgs 2026-06-23 17:14:40 -04:00
Owen
d78223b94f Fix import to be private 2026-06-23 17:14:40 -04:00
Owen Schwartz
963e9da7dd Merge pull request #3213 from ivenos/rename-to-compose-yaml
Rename docker-compose.yml to compose.yaml
2026-06-23 12:03:56 -07:00
Owen Schwartz
2cbc88fa05 Merge pull request #3326 from fosrl/dependabot/npm_and_yarn/js-yaml-4.2.0
Bump js-yaml from 4.1.1 to 4.2.0
2026-06-23 12:02:49 -07:00
Owen Schwartz
65bad456cb Merge pull request #3327 from fosrl/crowdin_dev
New Crowdin updates
2026-06-23 12:02:11 -07:00
Owen
f48a4f7bc0 Enforce strick query params
Fixes #3313
2026-06-23 12:22:47 -04:00
Owen
ce3c2f7583 Fix #3314 2026-06-23 12:05:47 -04:00
Owen
51c357e6c7 Merge branch 'main' into dev 2026-06-23 11:30:46 -04:00
Owen Schwartz
7ae29612d4 Merge pull request #3302 from RitwijParmar/codex/resource-update-inline-policy-response
Fix inline policy fields in resource update response
2026-06-23 08:30:31 -07:00
Owen
da794adb7d Merge branch 'main' into dev 2026-06-23 11:26:03 -04:00
Owen
8004ae6870 Use the policy when updating rule
Fixes #3273
2026-06-23 11:25:53 -04:00
Owen Schwartz
1bff7bbc2f Merge pull request #3315 from fosrl/dependabot/npm_and_yarn/nodemailer-9.0.1
Bump nodemailer from 8.0.9 to 9.0.1
2026-06-23 07:58:58 -07:00
Owen Schwartz
50db5695fc Merge pull request #3264 from fosrl/dependabot/npm_and_yarn/esbuild-0.28.1
Bump esbuild from 0.28.0 to 0.28.1
2026-06-23 07:57:47 -07:00
Owen Schwartz
babd90ae71 New translations en-us.json (Norwegian Bokmal)
[ci skip]
2026-06-22 15:22:46 -07:00
Owen Schwartz
f7050ef989 New translations en-us.json (Chinese Simplified)
[ci skip]
2026-06-22 15:22:44 -07:00
Owen Schwartz
b2778a2c49 New translations en-us.json (Turkish)
[ci skip]
2026-06-22 15:22:42 -07:00
Owen Schwartz
096940a152 New translations en-us.json (Russian)
[ci skip]
2026-06-22 15:22:40 -07:00
Owen Schwartz
73eb07de71 New translations en-us.json (Portuguese)
[ci skip]
2026-06-22 15:22:38 -07:00
Owen Schwartz
74ef844e27 New translations en-us.json (Polish)
[ci skip]
2026-06-22 15:22:36 -07:00
Owen Schwartz
c58968536d New translations en-us.json (Dutch)
[ci skip]
2026-06-22 15:22:34 -07:00
Owen Schwartz
228efacfe0 New translations en-us.json (Korean)
[ci skip]
2026-06-22 15:22:32 -07:00
Owen Schwartz
1262030abb New translations en-us.json (Italian)
[ci skip]
2026-06-22 15:22:30 -07:00
Owen Schwartz
b07fe6d18b New translations en-us.json (German)
[ci skip]
2026-06-22 15:22:28 -07:00
Owen Schwartz
63c3ee623b New translations en-us.json (Czech)
[ci skip]
2026-06-22 15:22:26 -07:00
Owen Schwartz
18ec6c8d92 New translations en-us.json (Bulgarian)
[ci skip]
2026-06-22 15:22:24 -07:00
Owen Schwartz
d8acccbde4 New translations en-us.json (Spanish)
[ci skip]
2026-06-22 15:22:22 -07:00
Owen Schwartz
37eaf34e4d New translations en-us.json (French)
[ci skip]
2026-06-22 15:22:20 -07:00
dependabot[bot]
cfb63f9742 Bump js-yaml from 4.1.1 to 4.2.0
Bumps [js-yaml](https://github.com/nodeca/js-yaml) from 4.1.1 to 4.2.0.
- [Changelog](https://github.com/nodeca/js-yaml/blob/master/CHANGELOG.md)
- [Commits](https://github.com/nodeca/js-yaml/compare/4.1.1...4.2.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-22 21:04:39 +00:00
Owen Schwartz
c76b4555e1 Merge pull request #3316 from fosrl/dependabot/npm_and_yarn/form-data-4.0.6
Bump form-data from 4.0.5 to 4.0.6
2026-06-22 14:03:06 -07:00
Owen Schwartz
c25bfbad27 Merge pull request #3317 from fosrl/dependabot/github_actions/actions/checkout-7.0.0
Bump actions/checkout from 6.0.2 to 7.0.0
2026-06-22 14:02:51 -07:00
Owen Schwartz
44782f8963 Merge pull request #3323 from fosrl/dependabot/go_modules/install/go-install-dependencies-4dfeb96e78
Bump golang.org/x/term from 0.43.0 to 0.44.0 in /install in the go-install-dependencies group
2026-06-22 14:02:31 -07:00
Owen Schwartz
e6f7cd6da9 Merge pull request #3206 from gmpinder/fix-idp-delete
fix: Add DELETE /idp/{idpId} to integration API
2026-06-22 14:01:47 -07:00
Owen Schwartz
19faa3a29c Merge pull request #3223 from Adityakk9031/#2867
fix: request logs not loading on initial page open in Community Editi…
2026-06-22 14:00:29 -07:00
Owen
c284dc2e83 Merge branch 'Fredkiss3-refactor/show-if-client-needs-update' into dev 2026-06-22 16:58:55 -04:00
Owen
1b634955d8 Merge branch 'refactor/show-if-client-needs-update' of github.com:Fredkiss3/pangolin into dev 2026-06-22 16:58:50 -04:00
Fred KISSIE
be888c3fc1 💄 Show the latest new update in machine client table 2026-06-22 16:57:47 -04:00
Fred KISSIE
3f2bb42221 ♻️ lt instead of lte 2026-06-22 16:57:47 -04:00
Fred KISSIE
5dc3ae4c7f ♻️ sites & clients should not get latest versions on the server 2026-06-22 16:57:45 -04:00
Fred KISSIE
ffb6c64de0 💄 Show updates available in the frontend, on sites & user devices 2026-06-22 16:57:08 -04:00
Fred KISSIE
2cbc6fb128 🏷️ types 2026-06-22 16:57:08 -04:00
Fred KISSIE
75084028d7 ♻️ Remove queries that prefetch 1000 users/roles in private resources form 2026-06-22 16:57:08 -04:00
Owen
f44a7c55dd Merge branch 'refactor/show-if-client-needs-update' of github.com:Fredkiss3/pangolin into Fredkiss3-refactor/show-if-client-needs-update 2026-06-22 16:56:52 -04:00
Owen Schwartz
72fa1d6a14 Merge pull request #3325 from fosrl/queue
Improve performance of rebuild functions
2026-06-22 13:49:20 -07:00
Owen
c3820a4e70 Add missing queuing 2026-06-22 16:47:52 -04:00
Owen
6b56c00782 Pull the listing out of the queue 2026-06-22 15:24:31 -04:00
Owen
60c1b572ba Add drizzle indexes to match db 2026-06-22 15:12:07 -04:00
Owen
604dee9aa5 Batch get olm ids 2026-06-22 15:12:07 -04:00
Owen
ee42846c90 Add batch messaging functions to rebuild function 2026-06-22 15:12:07 -04:00
copilot-swe-agent[bot]
22ac711dc6 refactor: tighten ws batch typing and queue cleanup logging 2026-06-22 15:12:07 -04:00
copilot-swe-agent[bot]
d09668b20b feat: batch redis ws direct messages and dedupe rebuild queue jobs 2026-06-22 15:12:07 -04:00
Owen
16abe98fd9 Add queue 2026-06-22 15:12:07 -04:00
copilot-swe-agent[bot]
d240201361 Initial plan 2026-06-22 15:12:07 -04:00
Josh Voyles
b7081aff11 fix: remove no-op autoFinalizeStatement wrapper and redundant busy_timeout (#2120)
better-sqlite3 11.x exposes no Statement.finalize() — the wrapper threw and
swallowed a TypeError on every query (verified: 'Statement.finalize exists:
undefined' in the runner image) while adding +122% per-statement overhead
(3.90 -> 8.66 us/op, 200k-op in-container microbench) and freeing nothing.
Statement lifecycle is GC-managed by the driver; drizzle-orm prepares fresh
per query, so nothing accumulates unbounded.

busy_timeout=5000 duplicates better-sqlite3's default timeout option, which
already arms sqlite3_busy_timeout(db, 5000) at open (lib/database.js).

With ENABLE_SQLITE_WAL_MODE unset the driver is now runtime-identical to
pre-1.18.3 (zero pragmas). The env-gated WAL block stays: journal_mode is
sticky in the DB file, so removing it would strand opted-in databases on
WAL+synchronous=FULL.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-22 15:11:51 -04:00
Josh Voyles
a55fb21e53 fix(sqlite): remove cache_size and mmap_size PRAGMAs (#2120)
A 64 MB page cache plus a 256 MB memory-mapped region inflate RSS and
cause page-cache thrashing on small (~1 GB) instances. The PRAGMAs were
added to reduce event-loop blocking on TraefikConfigManager JOINs but
the memory cost outweighs the I/O benefit on the deployment shapes that
hit #2120. Leave SQLite on its conservative defaults.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-22 15:11:51 -04:00
copilot-swe-agent[bot]
e5e7b79712 test: add normalized ASN validation coverage 2026-06-22 15:11:51 -04:00
copilot-swe-agent[bot]
de48a0529e refactor: normalize ASN validation value once 2026-06-22 15:11:51 -04:00
copilot-swe-agent[bot]
3f37408dae fix: allow ALL ASN values in policy rule validation 2026-06-22 15:11:51 -04:00
copilot-swe-agent[bot]
a2882857ff Initial plan 2026-06-22 15:11:51 -04:00
Owen
476d92b3ac Convert things to regional cache 2026-06-22 15:11:51 -04:00
Owen
bf604f25e9 Show the input validation in the error report 2026-06-22 15:11:50 -04:00
Owen
34a0d2a68b Remove NoNewPrivileges
Fixes https://github.com/fosrl/newt/issues/383
2026-06-22 15:11:50 -04:00
Owen Schwartz
62c7e0a13e Merge pull request #3251 from kshitijshresth/fix-path-rule-regex-escaping
Fix unescaped regex metacharacters in PATH rule matching causing request failures
2026-06-22 07:40:00 -07:00
dependabot[bot]
753358a17d Bump golang.org/x/term in /install in the go-install-dependencies group
Bumps the go-install-dependencies group in /install with 1 update: [golang.org/x/term](https://github.com/golang/term).


Updates `golang.org/x/term` from 0.43.0 to 0.44.0
- [Commits](https://github.com/golang/term/compare/v0.43.0...v0.44.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-22 14:32:24 +00:00
Owen Schwartz
c859393418 Merge pull request #3225 from fosrl/chore/dependabot-single-pr-groups
chore(dependabot): group dependency updates into single PRs per ecosystem
2026-06-22 07:31:25 -07:00
dependabot[bot]
d747b45f0b Bump actions/checkout from 6.0.2 to 7.0.0
Bumps [actions/checkout](https://github.com/actions/checkout) from 6.0.2 to 7.0.0.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](de0fac2e45...9c091bb21b)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-22 01:33:16 +00:00
dependabot[bot]
a24091257a Bump form-data from 4.0.5 to 4.0.6
Bumps [form-data](https://github.com/form-data/form-data) from 4.0.5 to 4.0.6.
- [Changelog](https://github.com/form-data/form-data/blob/master/CHANGELOG.md)
- [Commits](https://github.com/form-data/form-data/compare/v4.0.5...v4.0.6)

---
updated-dependencies:
- dependency-name: form-data
  dependency-version: 4.0.6
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-21 19:40:11 +00:00
dependabot[bot]
1c60041390 Bump nodemailer from 8.0.9 to 9.0.1
Bumps [nodemailer](https://github.com/nodemailer/nodemailer) from 8.0.9 to 9.0.1.
- [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.9...v9.0.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-21 09:46:17 +00:00
Ritwij Aryan Parmar
95c3f74a33 Fix inline policy fields in resource update response 2026-06-17 14:36:34 -04:00
Owen Schwartz
16c0f4eef4 Merge pull request #3277 from fosrl/dev
Fix middleware and suppoter footer
2026-06-14 14:44:33 -07:00
Owen
a08c6d70fe Comment out 2026-06-14 14:44:08 -07:00
miloschwartz
a6568692b7 force set supporter status to true in server info endpoint 2026-06-14 14:40:37 -07:00
Owen
a1196d3da6 Remove supporter warning 2026-06-14 14:34:39 -07:00
Owen
70bc4c0b30 Remove the path rewrite from the next route 2026-06-14 14:30:16 -07:00
Owen Schwartz
a0fef89031 Merge pull request #3276 from fosrl/dev
Rewrite headers
2026-06-14 14:13:54 -07:00
Owen
ea1badf4e0 Add middleware for rewriting host headers 2026-06-14 12:04:02 -07:00
Owen Schwartz
f15654ed11 Merge pull request #3275 from fosrl/dev
Fill in missing ui urls from the passed params
2026-06-14 11:36:01 -07:00
Owen
4435a669a6 Fill in missing ui urls from the passed params 2026-06-14 11:35:27 -07:00
Owen Schwartz
0b41fe3d49 Merge pull request #3268 from fosrl/dev
Send browser gateway rsources to remote nodes
2026-06-14 11:11:06 -07:00
Owen
90eceb457a Clean up url passing 2026-06-14 11:10:05 -07:00
Owen
f39cbc9bf4 Add same signature to oss 2026-06-14 11:03:14 -07:00
Owen
50da863bb7 Add maintence page support for remote nodes 2026-06-13 21:45:52 -07:00
Owen
c6ddd5c402 Open up holepunch requirements 2026-06-13 14:14:34 -07:00
Owen
0fb5ace9c7 Support the browser gateways on the remote nodes 2026-06-13 14:08:03 -07:00
dependabot[bot]
cedccd8cdb Bump esbuild from 0.28.0 to 0.28.1
Bumps [esbuild](https://github.com/evanw/esbuild) from 0.28.0 to 0.28.1.
- [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.28.0...v0.28.1)

---
updated-dependencies:
- dependency-name: esbuild
  dependency-version: 0.28.1
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-13 01:23:46 +00:00
Owen Schwartz
b9db0a4490 Merge pull request #3261 from fosrl/dev
1.19.2
2026-06-12 15:02:58 -07:00
Owen
39f40e5160 Allow missing agent host 2026-06-12 15:01:09 -07:00
Owen
3fd5c98def Fix #3252 2026-06-12 14:44:45 -07:00
Owen
5a8a48f9bf Enforce the action inside of the function 2026-06-12 14:22:17 -07:00
Owen
471ae98204 Pull roles from resource policies
Fixes #3256
2026-06-12 14:12:01 -07:00
Owen Schwartz
d985bfd3a6 Merge pull request #3260 from fosrl/crowdin_dev
New Crowdin updates
2026-06-12 13:58:34 -07:00
Owen Schwartz
aab51a999c New translations en-us.json (Norwegian Bokmal)
[ci skip]
2026-06-12 13:53:58 -07:00
Owen Schwartz
ae4f5aa58d New translations en-us.json (Chinese Simplified)
[ci skip]
2026-06-12 13:53:57 -07:00
Owen Schwartz
70d5f55437 New translations en-us.json (Turkish)
[ci skip]
2026-06-12 13:53:55 -07:00
Owen Schwartz
08df4b93aa New translations en-us.json (Russian)
[ci skip]
2026-06-12 13:53:54 -07:00
Owen Schwartz
61f0bc95c7 New translations en-us.json (Portuguese)
[ci skip]
2026-06-12 13:53:52 -07:00
Owen Schwartz
5b0f79a8bc New translations en-us.json (Polish)
[ci skip]
2026-06-12 13:53:50 -07:00
Owen Schwartz
86ff272095 New translations en-us.json (Dutch)
[ci skip]
2026-06-12 13:53:49 -07:00
Owen Schwartz
ef0575cc36 New translations en-us.json (Korean)
[ci skip]
2026-06-12 13:53:48 -07:00
Owen Schwartz
30a6889ba8 New translations en-us.json (Italian)
[ci skip]
2026-06-12 13:53:46 -07:00
Owen Schwartz
ce43e717d5 New translations en-us.json (German)
[ci skip]
2026-06-12 13:53:44 -07:00
Owen Schwartz
1f0361e687 New translations en-us.json (Czech)
[ci skip]
2026-06-12 13:53:43 -07:00
Owen Schwartz
07ae57ea72 New translations en-us.json (Bulgarian)
[ci skip]
2026-06-12 13:53:41 -07:00
Owen Schwartz
fc982232d6 New translations en-us.json (Spanish)
[ci skip]
2026-06-12 13:53:40 -07:00
Owen Schwartz
afaf5f976e New translations en-us.json (French)
[ci skip]
2026-06-12 13:53:38 -07:00
Owen
a8ca28acb2 Add default for target type
Fixes #3247
2026-06-12 11:29:16 -07:00
kshitijshresth
b136bd2246 Escape regex metacharacters in PATH rule wildcard matching
isValidUrlGlobPattern accepts characters like ( ) [ ] { } | . + ^ $ in PATH rule values, but isPathAllowed converted wildcard segments to regex without escaping them. A rule value such as /(api* produced an invalid regex and threw on every request to the resource, surfacing as a 500 from verifySession. Literal characters like . and + also changed matching semantics. isPathAllowed is extracted to server/lib/pathMatch.ts as a pure module, metacharacters are escaped before wildcard substitution, compiled segment regexes are cached, and the test suite now imports the real implementation instead of a stale copy, with added coverage for special characters.
2026-06-12 11:21:21 +03:00
Owen Schwartz
d9952b0762 Merge pull request #3250 from fosrl/dev
1.19.1
2026-06-11 22:05:24 -07:00
Owen
935593885a Adjust 1.19 and add 1.19.1 to ensure sso not null 2026-06-11 22:01:20 -07:00
miloschwartz
3fcfd3304f fix address input width 2026-06-11 18:34:22 -07:00
Owen Schwartz
6e271028f3 Merge pull request #3245 from fosrl/dev
Bugfixes
2026-06-11 16:17:41 -07:00
Owen
820f66e58f Properly hide things with disable enterprise flag 2026-06-11 16:10:29 -07:00
Owen
b0fdc10e06 Properly hide things with disable enterprise flag 2026-06-11 16:01:32 -07:00
miloschwartz
b82b41ed26 fix migration 2026-06-11 15:02:29 -07:00
miloschwartz
3e977ba00d make paid alert position more consistent on resource 2026-06-11 12:38:08 -07:00
Owen Schwartz
a724b07846 Merge pull request #3244 from fosrl/dev
fix paywalling
2026-06-11 12:27:49 -07:00
Owen
5f0bc71bcd Merge branch 'main' into dev 2026-06-11 12:26:31 -07:00
miloschwartz
aea7827c1a fix paywalling 2026-06-11 12:26:01 -07:00
Fred KISSIE
7a275c86c2 Merge branch 'dev' into refactor/show-if-client-needs-update 2026-06-11 21:05:31 +02:00
Fred KISSIE
4b703b5c11 💄 Show the latest new update in machine client table 2026-06-11 20:58:23 +02:00
Owen Schwartz
d865c4c55b Merge pull request #3242 from fosrl/dev
Use ssh like mode host
2026-06-11 11:29:45 -07:00
Owen
5baf0c3c09 Use ssh like mode host 2026-06-11 11:11:50 -07:00
Fred KISSIE
1b6e9e8cfe ♻️ lt instead of lte 2026-06-11 19:55:48 +02:00
Owen Schwartz
cfe33eb974 Merge pull request #3241 from fosrl/dev
dev
2026-06-10 21:47:44 -07:00
Owen
71273e1b1c Try to fix large query problem 2026-06-10 21:41:34 -07:00
Owen
02f6e2a8c3 Add ; fix lint 2026-06-10 20:56:26 -07:00
Owen Schwartz
3cc244a1d3 Merge pull request #3240 from fosrl/dev
Fix small bugs with paid features, ui, docs
2026-06-10 20:49:59 -07:00
Owen
1d9c4dd9e2 Fix padding 2026-06-10 20:46:53 -07:00
Owen
b9dd0c8e43 Add advantech install link 2026-06-10 20:46:43 -07:00
Owen
cd052976eb Properly paywall the edit policy screen 2026-06-10 20:38:59 -07:00
Owen
cc498f0e33 Properly paywall ui for labels 2026-06-10 20:32:07 -07:00
Owen
1a942937e6 Remove precheck on websocket for now 2026-06-10 20:24:41 -07:00
Owen
d81d1a6b7f Merge branch 'dev' of github.com:fosrl/pangolin into dev 2026-06-10 20:24:22 -07:00
Owen
f64d04e827 Add loading back to create resource 2026-06-10 18:23:01 -07:00
miloschwartz
540aee3fe2 update docs links 2026-06-10 17:52:42 -07:00
Owen Schwartz
10542d7282 Merge pull request #3239 from fosrl/dev
1.19.0
2026-06-10 16:50:32 -07:00
Owen
b1d52ad1a3 Update tiers 2026-06-10 16:27:25 -07:00
Owen
ce2fbef805 Filter only newt sites in the browser gateway 2026-06-10 16:16:42 -07:00
Owen
e312b31e02 Merge branch 'dev' of github.com:fosrl/pangolin into dev 2026-06-10 15:50:52 -07:00
Owen
bc156c715d 24 hour requirement for updates 2026-06-10 15:50:43 -07:00
miloschwartz
9a4c1f23c6 support remove server admin 2026-06-10 15:10:58 -07:00
Fred KISSIE
fe55956079 ♻️ sites & clients should not get latest versions on the server 2026-06-10 22:58:42 +02:00
Fred KISSIE
4cd0b9a0bb 💄 Show updates available in the frontend, on sites & user devices 2026-06-10 22:57:55 +02:00
Fred KISSIE
ab4d567af9 🏷️ types 2026-06-10 20:56:24 +02:00
miloschwartz
6921447fab fix typo 2026-06-10 11:55:20 -07:00
Owen
d47449b082 Add notes about inline policy to api endpoints 2026-06-10 10:24:31 -07:00
Owen
665806dfe8 Add some documentation; pull the override values 2026-06-10 10:03:16 -07:00
Owen
e248571268 Merge branch 'dev' of github.com:fosrl/pangolin into dev 2026-06-09 22:02:24 -07:00
miloschwartz
fcf03854ff fix tag input wrapping 2026-06-09 22:01:13 -07:00
Owen
dd1fba4e45 Also clear the roles and users 2026-06-09 21:59:30 -07:00
miloschwartz
a1ab8d8f35 standardize client titles 2026-06-09 21:47:15 -07:00
miloschwartz
c789e967db standardize client titles 2026-06-09 21:36:54 -07:00
Owen
d870b9ff49 Drop the not null on resource columns 2026-06-09 21:36:27 -07:00
miloschwartz
9c09019ddb add protocol filter 2026-06-09 21:33:56 -07:00
Owen
9d88683fc5 Reset resource info when on inline policy 2026-06-09 21:28:25 -07:00
miloschwartz
dd2c9f2a02 check resource policy in verifyResourceAccess middleware 2026-06-09 17:52:31 -07:00
miloschwartz
bdb38db5bc fix form responsiveness 2026-06-09 16:52:18 -07:00
Owen
96a54fc9cc Fix import issue in migrations 2026-06-09 16:51:55 -07:00
Owen
3a485f74f1 Move session migration out of the loop 2026-06-09 16:16:14 -07:00
Owen
92b0340324 Merge branch 'dev' of github.com:fosrl/pangolin into dev 2026-06-09 16:10:35 -07:00
miloschwartz
9257ac01c7 add learn more links to auto update 2026-06-09 16:08:07 -07:00
Owen
4d1d0d9fcb Add warning if we cant reach the vnc server 2026-06-09 16:02:52 -07:00
Owen
f186e7e99e Dont allow asn or country without having maxmind 2026-06-09 16:02:52 -07:00
miloschwartz
1aa6e3511f dont show policy on tcp/udp resources 2026-06-09 15:56:24 -07:00
miloschwartz
fb6f5b3953 form layout improvements 2026-06-09 15:42:28 -07:00
Owen
c85a7f6ac5 Migrate unkown openapi response from string to {} 2026-06-09 15:35:08 -07:00
Owen
dd54be523f Dont need to check user exists for the whitelist 2026-06-09 15:26:35 -07:00
Owen
d57f064d4c Fix spelling 2026-06-09 15:26:35 -07:00
miloschwartz
34799b7de2 move maintenance page config to tab 2026-06-09 15:07:55 -07:00
miloschwartz
20a66bba6f fix resource context updating problem 2026-06-09 14:49:57 -07:00
miloschwartz
cdb43d9658 dont set whitelist until click set button in dialog 2026-06-09 14:36:50 -07:00
miloschwartz
6581ccafa3 fix toggle on pin or passcode not working on policy form 2026-06-09 14:34:58 -07:00
miloschwartz
a3a45b4239 add safe read 2026-06-09 14:09:36 -07:00
Owen
d6634b6e8a Add types 2026-06-09 12:16:00 -07:00
Owen
1089cfbacc Update query to be more efficient 2026-06-09 11:54:46 -07:00
Owen
1907a3c93b Link to primary org only when you can see billing 2026-06-09 10:33:42 -07:00
Fred KISSIE
38203e522b ♻️ Remove queries that prefetch 1000 users/roles in private resources form 2026-06-09 19:29:00 +02:00
miloschwartz
407ba567a0 various visual changes 2026-06-08 22:07:53 -07:00
Owen
f28571629f Make sure the pamMode is push for host resources 2026-06-08 21:54:06 -07:00
Owen
5a575c916b Handle backward compatability 2026-06-08 21:11:57 -07:00
Owen
9a7e534b10 Ssh session closed card 2026-06-08 17:44:48 -07:00
Owen
42974d1739 Make sure the skip to idp is pulled 2026-06-08 17:41:59 -07:00
Owen
780e8babe4 Perfect toolbar 2026-06-08 17:39:07 -07:00
Owen
2c7b8006cf Add gray bar 2026-06-08 16:07:36 -07:00
Owen
35066c1388 Add pulldown toolbar 2026-06-08 16:00:38 -07:00
miloschwartz
135a5d38af make form grids more consistent 2026-06-08 16:00:30 -07:00
Owen
1b7c1ffa70 Set the target port from the resource 2026-06-08 15:39:26 -07:00
Owen
641f643d2d Prefil the port with the best guess port 2026-06-08 15:39:26 -07:00
Owen
b4ecfceb5e Show more information about error 2026-06-08 15:39:25 -07:00
Owen
08a84d4bb1 Add some connection feedback 2026-06-08 15:39:25 -07:00
Owen
4dbad7ab24 Close the tab when exiting 2026-06-08 15:39:25 -07:00
miloschwartz
859c0c9477 add description text to share link path input 2026-06-08 15:33:12 -07:00
miloschwartz
d294bf8534 support uploading csv or txt to sudo commands and groups 2026-06-08 15:30:03 -07:00
miloschwartz
3c8fea382f improve unix group and sudo commands inputs 2026-06-08 14:36:10 -07:00
Owen
b81bfcfcee Fix type error 2026-06-08 12:21:43 -07:00
Milo Schwartz
56c415ca05 Merge pull request #3219 from Fredkiss3/refactor/standardize-clear-buttons
feat: make clear filter buttons more consistent accross tables
2026-06-08 12:07:55 -07:00
Owen
74fdcceace Reconnect newts when a exit node comes back online 2026-06-08 12:02:12 -07:00
Owen
7dec8ba998 Add exit node if the sites dont have one 2026-06-08 12:02:12 -07:00
miloschwartz
c9dc6affe7 Merge branch 'dev' into resource-policies-restyle 2026-06-08 12:00:08 -07:00
miloschwartz
8fe45ba78c prevent duplicate label names 2026-06-08 11:59:15 -07:00
Fred KISSIE
934886caea Merge branch 'dev' into refactor/standardize-clear-buttons 2026-06-08 20:42:11 +02:00
miloschwartz
fae258b145 add labels to user-resources query 2026-06-08 10:55:24 -07:00
miloschwartz
9f224f655f Merge branch 'resource-policies-restyle' into dev 2026-06-08 10:38:13 -07:00
miloschwartz
aea7df7dc2 rename share links 2026-06-08 10:37:46 -07:00
miloschwartz
3b675f7de1 policies and policy on resource structure in a good place 2026-06-07 12:19:33 -07:00
Owen
8daf7c2872 Rename and add browser target update 2026-06-07 12:07:08 -07:00
Owen
c394490473 Update browser targets 2026-06-07 10:43:16 -07:00
Marc Schäfer
92d611df9a chore(dependabot): group dependency updates into single PRs per ecosystem 2026-06-07 11:10:53 +02:00
Owen
3b6b78b3e1 Update traefik config 2026-06-06 16:14:20 -07:00
miloschwartz
aa47f522ef move toggle on general page 2026-06-06 15:34:34 -07:00
Owen
8658198a93 Remove unnessicary auth token 2026-06-06 13:47:23 -07:00
Owen
4b770d1385 Fix issues 2026-06-06 13:34:24 -07:00
miloschwartz
cd4d7372a0 Merge branch 'resource-policies-restyle' into dev 2026-06-06 12:27:59 -07:00
Owen
dc8243cb51 Fix form rendering issue 2026-06-06 12:27:14 -07:00
miloschwartz
7b1f8d98f3 make the rule rows draggable 2026-06-06 12:27:11 -07:00
miloschwartz
dd8bcbb3e3 first pass restyle of auth methods and rules 2026-06-05 21:04:03 -07:00
Owen
d1af7a153f Enforece some more things on the types 2026-06-05 16:57:53 -07:00
Owen
13efa47db7 Merge branch 'dev' of github.com:fosrl/pangolin into dev 2026-06-05 16:12:46 -07:00
Owen
69bd61c308 Update migrations 2026-06-05 16:02:28 -07:00
Owen
7b7ff51289 Add target mode and auth token 2026-06-05 15:37:21 -07:00
Owen
772ac8af73 Remove browser gateway targets for regular targets 2026-06-05 15:30:42 -07:00
miloschwartz
8ee520dbb5 form validation improvements 2026-06-05 14:55:27 -07:00
Owen
8e5d9e94a9 Fix delete site only working on newt site 2026-06-05 14:37:44 -07:00
Owen
c9cb28af45 Rename to public-policies 2026-06-05 14:30:36 -07:00
Fred KISSIE
a994f8ff07 💄 Column filter buttons for log tables 2026-06-05 21:47:08 +02:00
miloschwartz
ea8eaf9736 adjust targets input styles 2026-06-05 12:43:00 -07:00
miloschwartz
b78db3daef move policies routes 2026-06-05 12:43:00 -07:00
Owen
7cf3f8df92 Rename proxy -> public 2026-06-05 12:12:27 -07:00
Owen
f2b5cff3f9 Fix resource protection status showing wrong 2026-06-05 12:12:27 -07:00
Owen
6de9ab8f05 Add watermark on missing login pages 2026-06-05 12:12:27 -07:00
Owen
ad0e800d8d Fix validation error and bring alias back to table 2026-06-05 12:12:27 -07:00
Aditya kumar singh
13b691fd7d fix: request logs not loading on initial page open in Community Edition (#2867) 2026-06-06 00:34:48 +05:30
miloschwartz
65470fb64b adjust styles 2026-06-05 12:04:33 -07:00
miloschwartz
f23142336b enable editing resource policy niceID 2026-06-05 11:57:55 -07:00
miloschwartz
2da4987cd3 rename policies 2026-06-05 11:52:51 -07:00
miloschwartz
253ba554a2 fix resources cell styling 2026-06-05 11:46:30 -07:00
Fred KISSIE
95ce91d94b Merge branch 'dev' into refactor/standardize-clear-buttons 2026-06-05 20:21:34 +02:00
Fred KISSIE
a4548fd874 💄 Break all text 2026-06-05 19:59:28 +02:00
Fred KISSIE
eb03fb7060 ♻️ standardize http request log data-tables 2026-06-05 19:28:30 +02:00
miloschwartz
add9b8dfb0 many minor visual improvements 2026-06-04 22:25:18 -07:00
Owen
2adb7b64cb Add the resource name 2026-06-04 22:10:38 -07:00
Owen
84fef5f1d6 Resource policy api backward compatability 2026-06-04 22:02:42 -07:00
Owen
def1e9c851 Add region back to the rules 2026-06-04 21:28:29 -07:00
Owen
67b08ca61e Properly do disable enterprise features this time 2026-06-04 21:18:04 -07:00
Owen
614df75880 Add policy to blueprints 2026-06-04 21:18:04 -07:00
Owen
676cf37ee2 Make sure things are paywalled in the blueprints 2026-06-04 21:18:04 -07:00
Owen
6b96e3dce6 Make sure the disableEnterpriseFeatures works 2026-06-04 21:18:04 -07:00
miloschwartz
b67037e2ea use default animation speed on dialog 2026-06-04 18:19:23 -07:00
miloschwartz
5a5b77cf62 update project cursor rules 2026-06-04 18:13:26 -07:00
miloschwartz
d2793dfad7 use react forms 2026-06-04 18:07:50 -07:00
miloschwartz
ff507f1275 use standard error alert 2026-06-04 17:44:45 -07:00
miloschwartz
6b04bcb383 translate strings in auth pages for ssh, vnc, and rdp 2026-06-04 17:35:43 -07:00
miloschwartz
b2f1115ef8 standardize and fix branding on new resources auth pages 2026-06-04 17:24:06 -07:00
Owen
567ef23ac4 Add initial advantech install commands 2026-06-04 16:59:58 -07:00
Owen
6affebc666 Finish adding ssh toggle 2026-06-04 16:59:58 -07:00
miloschwartz
889f78ddb8 use resource name in ssh/rdp/vnc page meta 2026-06-04 16:45:35 -07:00
Owen
9d3f96cf83 Add disable_private_http_placeholder 2026-06-04 16:41:07 -07:00
miloschwartz
e5d0673bbf prefill site field on create private resource when filtering sites 2026-06-04 16:30:14 -07:00
miloschwartz
0907c0346f remove check on oidc login 2026-06-04 16:24:29 -07:00
Owen
6420a90d08 Replace tab component 2026-06-04 16:21:30 -07:00
Owen Schwartz
7fa1180d10 Merge pull request #3221 from fosrl/dev
1.19.0-rc.1
2026-06-04 15:45:27 -07:00
Owen
769d36e289 Fix http resources not being pulled 2026-06-04 15:36:25 -07:00
Owen
a7a41b820e Add missing sshAccess key 2026-06-04 15:20:52 -07:00
Fred KISSIE
33fdc9a94f 🚧 wip: column filter button 2026-06-04 21:04:15 +02:00
Owen Schwartz
8b50f1fb65 Merge pull request #3218 from fosrl/dev
Fix installer
2026-06-04 11:21:59 -07:00
Owen
2d78a4b628 Fix installer 2026-06-04 11:21:40 -07:00
Fred KISSIE
c86026c941 ♻️ refactor 2026-06-04 20:09:07 +02:00
Fred KISSIE
db014e3446 ♻️ use the same clear filter text for clearing filters in the column filter buttons 2026-06-04 20:08:27 +02:00
Fred KISSIE
feb8045643 ♻️ refactor 2026-06-04 19:54:43 +02:00
Fred KISSIE
d485a09318 ♻️ use site label filter column 2026-06-04 19:45:54 +02:00
Fred KISSIE
9cff5f66b1 🚧 wip: site label column filter standardized 2026-06-04 19:40:24 +02:00
Owen Schwartz
527d4cc777 Merge pull request #3215 from fosrl/dev
1.19.0-rc.0
2026-06-04 10:34:20 -07:00
Owen Schwartz
01361884eb Potential fix for pull request finding 'CodeQL / Insecure randomness'
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
2026-06-04 10:33:15 -07:00
Owen
6c4cbcab5d Fix eslint errors 2026-06-04 10:22:29 -07:00
Owen Schwartz
aac25f0a53 Merge pull request #3214 from marcschaeferger/dev
Prevent cross-org site binding in target create/update
2026-06-04 10:11:53 -07:00
ivenos
89f3f3c8cd Rename docker-compose.yml to compose.yaml 2026-06-04 10:01:35 +02:00
miloschwartz
4a3c201741 improve mfa autofill 2026-06-03 21:54:55 -07:00
miloschwartz
f5ab837cce remove idp user if unassociate idp, warn, and fix create user form bug 2026-06-03 21:42:18 -07:00
Owen Schwartz
00ec7a5c66 Merge pull request #3211 from fosrl/crowdin_dev
New Crowdin updates
2026-06-03 21:40:33 -07:00
Owen Schwartz
03df4d03ed New translations en-us.json (Spanish)
[ci skip]
2026-06-03 21:39:34 -07:00
Owen Schwartz
8488edd707 New translations en-us.json (Norwegian Bokmal)
[ci skip]
2026-06-03 21:39:32 -07:00
Owen Schwartz
920dbba2a3 New translations en-us.json (Chinese Simplified)
[ci skip]
2026-06-03 21:39:31 -07:00
Owen Schwartz
71e065a8bc New translations en-us.json (Turkish)
[ci skip]
2026-06-03 21:39:29 -07:00
Owen Schwartz
e125c762b2 New translations en-us.json (Russian)
[ci skip]
2026-06-03 21:39:27 -07:00
Owen Schwartz
4f01f7c072 New translations en-us.json (Portuguese)
[ci skip]
2026-06-03 21:39:25 -07:00
Owen Schwartz
9a5ee9d489 New translations en-us.json (Polish)
[ci skip]
2026-06-03 21:39:24 -07:00
Owen Schwartz
665da931f1 New translations en-us.json (Dutch)
[ci skip]
2026-06-03 21:39:22 -07:00
Owen Schwartz
7595cf7ac7 New translations en-us.json (Korean)
[ci skip]
2026-06-03 21:39:20 -07:00
Owen Schwartz
aa6232d0fc New translations en-us.json (Italian)
[ci skip]
2026-06-03 21:39:18 -07:00
Owen Schwartz
beaf5dc843 New translations en-us.json (German)
[ci skip]
2026-06-03 21:39:17 -07:00
Owen Schwartz
7158855052 New translations en-us.json (Czech)
[ci skip]
2026-06-03 21:39:15 -07:00
Owen Schwartz
765b2d795f New translations en-us.json (Bulgarian)
[ci skip]
2026-06-03 21:39:13 -07:00
Owen Schwartz
66f00fcf94 New translations en-us.json (French)
[ci skip]
2026-06-03 21:39:11 -07:00
Owen
e408e735be Make alias cross compatable 2026-06-03 17:58:59 -07:00
Owen
e826d0dea6 Add better loading spinner 2026-06-03 17:41:56 -07:00
Owen
bc6fd0b399 Get user resources from the right table 2026-06-03 16:53:39 -07:00
Owen
d00b737412 Pull the sso from the policies as well 2026-06-03 16:16:42 -07:00
Owen
1f43713986 Fix saving the rules 2026-06-03 16:02:17 -07:00
Owen
cc5bec1d83 Pull the rules and the policy information 2026-06-03 15:33:15 -07:00
Owen
40125c717c Pull things in proper order 2026-06-03 14:52:36 -07:00
Owen
2b402f8fec Quiet logs 2026-06-03 14:48:51 -07:00
Owen
8e9071a336 Converting to use both inline and shared policy 2026-06-03 14:41:43 -07:00
Owen
18bcf40174 Merge branch 'dev' of github.com:fosrl/pangolin into dev 2026-06-03 13:50:54 -07:00
Owen Schwartz
42e9b913f1 Merge pull request #3201 from Fredkiss3/refactor/standardize-dropdowns
refactor: standardize dropdowns accross tables
2026-06-03 13:50:40 -07:00
Fred KISSIE
fcb73f78ea 🏷️ fix type imports 2026-06-03 20:23:32 +02:00
Fred KISSIE
a21569bd00 🏷️ fix types imports from a client component 2026-06-03 20:14:43 +02:00
Fred KISSIE
565727ad36 🏷️ import types correctly 2026-06-03 19:51:44 +02:00
Fred KISSIE
00dce19997 Merge branch 'dev' into refactor/standardize-dropdowns 2026-06-03 19:34:22 +02:00
Fred KISSIE
29717e19db ♻️ update machine client labels cell 2026-06-03 19:17:24 +02:00
Fred KISSIE
97aeee541a ♻️ Label table cell optimization 2026-06-03 19:15:44 +02:00
Gerald Pinder
44c16d69af fix: Add DELETE /idp/{idpId} to integration API 2026-06-03 12:48:34 -04:00
Owen
b70a2bee58 Native ssh push users is working 2026-06-02 22:00:29 -07:00
Owen
f2f56dc6c2 Properly paywall the new resource types 2026-06-02 18:06:42 -07:00
Owen
128db20755 Remove migration test 2026-06-02 17:13:10 -07:00
Owen
12cbd40596 Fix types 2026-06-02 16:56:58 -07:00
Owen
ffd0d17b58 Add proxy protocl support in blueprints 2026-06-02 16:42:26 -07:00
Owen
33fad57bf7 Restrict the number of sites in the api 2026-06-02 16:38:04 -07:00
Owen
8bcc130947 Make sure the right type of select shows 2026-06-02 16:33:05 -07:00
Owen
19feaf4bf2 Add the policy information into missing places 2026-06-02 15:47:55 -07:00
Owen
88ea4391e0 Show new types of resources right 2026-06-02 15:31:33 -07:00
Owen
fba37b7ad0 Add command to make other user server admin 2026-06-02 15:31:21 -07:00
Fred KISSIE
6c1798a8c5 💄 use checkbox for column filter with dropdown 2026-06-02 17:43:45 +02:00
Owen
b6d688f15e Support pin,pass,whitelist correctly on login 2026-06-01 21:34:39 -07:00
Owen
8a57d8dd9c Resource rules go first 2026-06-01 21:02:59 -07:00
Owen
8e0e32c2be Overriding is working 2026-06-01 20:54:37 -07:00
Owen
6b3a0a2113 Remove the admin from the picker 2026-06-01 20:33:37 -07:00
Owen
4d6ed7eec5 Pull from the policies to show to users 2026-06-01 17:49:09 -07:00
Owen
1625dd1add Include the new policy tables in the data 2026-06-01 17:04:33 -07:00
Owen
605dd2f3c9 Add tcp and udp specific pages 2026-06-01 16:05:20 -07:00
Owen
51bb149fd5 Update to latest badger 2026-06-01 15:27:34 -07:00
Owen
2ae4c29418 Add missing set 2026-06-01 15:27:30 -07:00
Owen
ba71016f87 Add inline policy migration 2026-06-01 15:18:40 -07:00
Owen
85c2bd807e Handle the new added mode column 2026-06-01 14:49:41 -07:00
Owen
517e1d15c8 Add 1.19.0 migrations 2026-06-01 14:42:32 -07:00
Owen
3d6d5f176a Hide verify button 2026-06-01 14:10:08 -07:00
Owen
5dd19edb56 Hold the hp error message until after 18 tries 2026-06-01 14:05:19 -07:00
Owen
c6a52ffc75 Dont run migration again when rc 2026-06-01 13:58:04 -07:00
Owen
09b2671759 Send hp error to olm 2026-06-01 13:57:54 -07:00
Owen
d11a244caa Push mode and sign key adjustments for native mode 2026-06-01 11:41:55 -07:00
miloschwartz
bf79768e05 update links 2026-05-31 20:20:37 -07:00
Owen
08a2923cfc Move proxy and client to public and private 2026-05-31 17:30:31 -07:00
Owen
b99e9a6468 Working on ui 2026-05-31 17:25:03 -07:00
Owen
cb2ee9c489 Fixing visual issues 2026-05-31 16:36:13 -07:00
Owen
c1d933259a Fix some ui form issues 2026-05-31 11:57:01 -07:00
Owen
3cf6abdf27 Rename internal resource -> private 2026-05-31 11:28:04 -07:00
Owen
0f2132e565 Merge branch 'main' into dev 2026-05-31 11:12:30 -07:00
Owen
5cc88dc73f Pull the session from badger 2026-05-31 11:11:26 -07:00
Owen Schwartz
ebe1c7a297 Improve OpenAPI response payload typing for Swagger data schemas (#3102)
* Fix custom parser OpenAPI types and add structured default response schema

Agent-Logs-Url: https://github.com/fosrl/pangolin/sessions/73990123-9c27-444b-bc6e-77e890a0d57c

Co-authored-by: oschwartz10612 <4999704+oschwartz10612@users.noreply.github.com>

* Document all registerPath responses and normalize OpenAPI parser schemas

Agent-Logs-Url: https://github.com/fosrl/pangolin/sessions/73990123-9c27-444b-bc6e-77e890a0d57c

Co-authored-by: oschwartz10612 <4999704+oschwartz10612@users.noreply.github.com>

* Add concrete OpenAPI data schemas for selected routes

Agent-Logs-Url: https://github.com/fosrl/pangolin/sessions/7b395a8e-7fae-4f4d-952e-4030fea08262

Co-authored-by: oschwartz10612 <4999704+oschwartz10612@users.noreply.github.com>

* Reformat generated OpenAPI response schemas for readability

Agent-Logs-Url: https://github.com/fosrl/pangolin/sessions/7b395a8e-7fae-4f4d-952e-4030fea08262

Co-authored-by: oschwartz10612 <4999704+oschwartz10612@users.noreply.github.com>

* Remove obsolete stoi import from blueprint OpenAPI route

Agent-Logs-Url: https://github.com/fosrl/pangolin/sessions/7b395a8e-7fae-4f4d-952e-4030fea08262

Co-authored-by: oschwartz10612 <4999704+oschwartz10612@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: oschwartz10612 <4999704+oschwartz10612@users.noreply.github.com>
2026-05-31 11:10:38 -07:00
Owen
0943cf5d4c Dont strip session 2026-05-30 12:10:06 -07:00
Owen
3b82ac568f Give auth feedback 2026-05-30 11:56:32 -07:00
Owen
b695f34dc8 Native ssh auth working 2026-05-30 11:54:01 -07:00
Owen
6df4bba3b6 Bump version 2026-05-29 17:12:26 -07:00
Owen
9f83c0a0e8 Show error right 2026-05-29 17:11:44 -07:00
Marc Schäfer
f617f93a94 test(middleware): add regression tests for cross-org site binding prevention
Test the org-match logic in verifySiteAccess:
- Same org: allowed
- Cross-org: rejected with 403
- No prior org context (site-only routes): check skipped, normal flow

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

Test security scenarios for both WireGuard and newt site types.

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

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

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

Signed-off-by: Marc Schäfer <git@marcschaeferger.de>
2026-05-29 22:44:16 +00:00
Owen
0ab1854125 Fix import 2026-05-29 15:38:37 -07:00
Owen
b071fa2c9f Be able to pull users from the proxy 2026-05-29 15:34:34 -07:00
Owen
8e2a79a0f5 Move to private 2026-05-29 15:23:40 -07:00
Owen
71756812b6 Adjusting the ui 2026-05-29 10:57:28 -07:00
Owen
76cd716caa Add user id 2026-05-29 10:57:16 -07:00
Shlee
b0d1291cff Installer: Bootstrap optional PostgreSQL/Redis (#3152)
* Make optional postgres and redis in installer
2026-05-29 09:43:59 -07:00
Owen
9617eb2bd7 Add login formatting 2026-05-28 21:38:40 -07:00
Owen
c1ef5b4fbe Add allowedDevOrigins 2026-05-28 21:23:55 -07:00
Owen
8e14bdec95 Remove cloud 2026-05-28 21:20:12 -07:00
dependabot[bot]
b26dfaf57f Bump the prod-minor-updates group with 9 updates
Bumps the prod-minor-updates group with 9 updates:

| Package | From | To |
| --- | --- | --- |
| [@aws-sdk/client-s3](https://github.com/aws/aws-sdk-js-v3/tree/HEAD/clients/client-s3) | `3.1047.0` | `3.1056.0` |
| [@hookform/resolvers](https://github.com/react-hook-form/resolvers) | `5.2.2` | `5.4.0` |
| [helmet](https://github.com/helmetjs/helmet) | `8.1.0` | `8.2.0` |
| [ioredis](https://github.com/luin/ioredis) | `5.10.1` | `5.11.0` |
| [next-intl](https://github.com/amannn/next-intl) | `4.12.0` | `4.13.0` |
| [pg](https://github.com/brianc/node-postgres/tree/HEAD/packages/pg) | `8.20.0` | `8.21.0` |
| [posthog-node](https://github.com/PostHog/posthog-js/tree/HEAD/packages/node) | `5.34.1` | `5.35.6` |
| [react-hook-form](https://github.com/react-hook-form/react-hook-form) | `7.75.0` | `7.76.1` |
| [ws](https://github.com/websockets/ws) | `8.20.1` | `8.21.0` |

Updates `@aws-sdk/client-s3` from 3.1047.0 to 3.1056.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.1056.0/clients/client-s3)

Updates `@hookform/resolvers` from 5.2.2 to 5.4.0
- [Release notes](https://github.com/react-hook-form/resolvers/releases)
- [Commits](https://github.com/react-hook-form/resolvers/compare/v5.2.2...v5.4.0)

Updates `helmet` from 8.1.0 to 8.2.0
- [Changelog](https://github.com/helmetjs/helmet/blob/main/CHANGELOG.md)
- [Commits](https://github.com/helmetjs/helmet/compare/v8.1.0...v8.2.0)

Updates `ioredis` from 5.10.1 to 5.11.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.10.1...v5.11.0)

Updates `next-intl` from 4.12.0 to 4.13.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.12.0...v4.13.0)

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

Updates `posthog-node` from 5.34.1 to 5.35.6
- [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.35.6/packages/node)

Updates `react-hook-form` from 7.75.0 to 7.76.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.75.0...v7.76.1)

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

---
updated-dependencies:
- dependency-name: "@aws-sdk/client-s3"
  dependency-version: 3.1056.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: prod-minor-updates
- dependency-name: "@hookform/resolvers"
  dependency-version: 5.4.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: prod-minor-updates
- dependency-name: helmet
  dependency-version: 8.2.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: prod-minor-updates
- dependency-name: ioredis
  dependency-version: 5.11.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: prod-minor-updates
- dependency-name: next-intl
  dependency-version: 4.13.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: prod-minor-updates
- dependency-name: pg
  dependency-version: 8.21.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: prod-minor-updates
- dependency-name: posthog-node
  dependency-version: 5.35.6
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: prod-minor-updates
- dependency-name: react-hook-form
  dependency-version: 7.76.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: prod-minor-updates
- dependency-name: ws
  dependency-version: 8.21.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-05-28 21:15:00 -07:00
dependabot[bot]
1a1c19b24e Bump the dev-patch-updates group with 4 updates
Bumps the dev-patch-updates group with 4 updates: [@tanstack/react-query-devtools](https://github.com/TanStack/query/tree/HEAD/packages/react-query-devtools), [@types/react](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react), [postcss](https://github.com/postcss/postcss) and [tsx](https://github.com/privatenumber/tsx).


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

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

Updates `postcss` from 8.5.14 to 8.5.15
- [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.14...8.5.15)

Updates `tsx` from 4.22.0 to 4.22.3
- [Release notes](https://github.com/privatenumber/tsx/releases)
- [Changelog](https://github.com/privatenumber/tsx/blob/master/release.config.cjs)
- [Commits](https://github.com/privatenumber/tsx/compare/v4.22.0...v4.22.3)

---
updated-dependencies:
- dependency-name: "@tanstack/react-query-devtools"
  dependency-version: 5.100.14
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-patch-updates
- dependency-name: "@types/react"
  dependency-version: 19.2.15
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-patch-updates
- dependency-name: postcss
  dependency-version: 8.5.15
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-patch-updates
- dependency-name: tsx
  dependency-version: 4.22.3
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-patch-updates
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-28 21:14:14 -07:00
dependabot[bot]
9d214b18af Bump stripe from 20.4.1 to 22.2.0
Bumps [stripe](https://github.com/stripe/stripe-node) from 20.4.1 to 22.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.4.1...v22.2.0)

---
updated-dependencies:
- dependency-name: stripe
  dependency-version: 22.2.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-28 21:14:14 -07:00
dependabot[bot]
e67b50b356 Bump lucide-react from 0.577.0 to 1.17.0
Bumps [lucide-react](https://github.com/lucide-icons/lucide/tree/HEAD/packages/lucide-react) from 0.577.0 to 1.17.0.
- [Release notes](https://github.com/lucide-icons/lucide/releases)
- [Commits](https://github.com/lucide-icons/lucide/commits/1.17.0/packages/lucide-react)

---
updated-dependencies:
- dependency-name: lucide-react
  dependency-version: 1.17.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-28 21:14:14 -07:00
rinseaid
616caf76cb Auto-create roles referenced in blueprints
When a blueprint references a role that doesn't exist, create it
automatically with default permissions (getOrg, getResource,
listResources) instead of throwing an error or silently dropping
the association.
2026-05-28 21:14:14 -07:00
Owen
9a1db4948b Shape the ssh/vnc/rdp login ui to match auth 2026-05-28 21:14:13 -07:00
Owen Schwartz
1215aa8122 Merge pull request #3184 from fosrl/dependabot/npm_and_yarn/prod-minor-updates-1701004488
Bump the prod-minor-updates group with 9 updates
2026-05-28 20:36:43 -07:00
Owen Schwartz
d318a756a8 Merge pull request #3183 from fosrl/dependabot/npm_and_yarn/dev-patch-updates-60744307c2
Bump the dev-patch-updates group with 4 updates
2026-05-28 20:36:17 -07:00
Owen Schwartz
b3c1e49c0c Merge pull request #3185 from fosrl/dependabot/npm_and_yarn/stripe-22.2.0
Bump stripe from 20.4.1 to 22.2.0
2026-05-28 20:35:52 -07:00
Owen Schwartz
dc12b00502 Merge pull request #3186 from fosrl/dependabot/npm_and_yarn/lucide-react-1.17.0
Bump lucide-react from 0.577.0 to 1.17.0
2026-05-28 20:35:39 -07:00
Owen Schwartz
5b814e37c4 Merge pull request #2434 from NHClaessens/feature-share-link-redirect-path
feat: Add path setting to share links (resourceAccessToken)
2026-05-28 20:28:28 -07:00
Owen
8483616b04 Unstage ignored files 2026-05-28 20:27:25 -07:00
Owen
ffe198839a Reset translations 2026-05-28 20:25:34 -07:00
NHClaessens
db5d1d4a16 Update postgres schema 2026-05-28 20:20:34 -07:00
NHClaessens
ad7dcddf24 Add translations 2026-05-28 20:20:33 -07:00
Owen
94408aad21 Add path onto redirectUrl 2026-05-28 20:19:19 -07:00
NHClaessens
b84a7996a9 Adjust validation to allow creation with (optional) path 2026-05-28 20:15:22 -07:00
Owen
a9b0bd8b47 Alter schema + add form field 2026-05-28 20:15:13 -07:00
Owen
a32acf7c69 Fix ui 2026-05-28 20:14:39 -07:00
Owen Schwartz
1e27acbf88 Merge pull request #2980 from rinseaid/blueprint-auto-create-roles
Auto-create roles referenced in blueprints
2026-05-28 20:10:53 -07:00
dependabot[bot]
4012cc658d Bump lucide-react from 0.577.0 to 1.17.0
Bumps [lucide-react](https://github.com/lucide-icons/lucide/tree/HEAD/packages/lucide-react) from 0.577.0 to 1.17.0.
- [Release notes](https://github.com/lucide-icons/lucide/releases)
- [Commits](https://github.com/lucide-icons/lucide/commits/1.17.0/packages/lucide-react)

---
updated-dependencies:
- dependency-name: lucide-react
  dependency-version: 1.17.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-29 01:44:09 +00:00
dependabot[bot]
84d7a87609 Bump stripe from 20.4.1 to 22.2.0
Bumps [stripe](https://github.com/stripe/stripe-node) from 20.4.1 to 22.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.4.1...v22.2.0)

---
updated-dependencies:
- dependency-name: stripe
  dependency-version: 22.2.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-29 01:43:42 +00:00
dependabot[bot]
9a92be532a Bump the prod-minor-updates group with 9 updates
Bumps the prod-minor-updates group with 9 updates:

| Package | From | To |
| --- | --- | --- |
| [@aws-sdk/client-s3](https://github.com/aws/aws-sdk-js-v3/tree/HEAD/clients/client-s3) | `3.1047.0` | `3.1056.0` |
| [@hookform/resolvers](https://github.com/react-hook-form/resolvers) | `5.2.2` | `5.4.0` |
| [helmet](https://github.com/helmetjs/helmet) | `8.1.0` | `8.2.0` |
| [ioredis](https://github.com/luin/ioredis) | `5.10.1` | `5.11.0` |
| [next-intl](https://github.com/amannn/next-intl) | `4.12.0` | `4.13.0` |
| [pg](https://github.com/brianc/node-postgres/tree/HEAD/packages/pg) | `8.20.0` | `8.21.0` |
| [posthog-node](https://github.com/PostHog/posthog-js/tree/HEAD/packages/node) | `5.34.1` | `5.35.6` |
| [react-hook-form](https://github.com/react-hook-form/react-hook-form) | `7.75.0` | `7.76.1` |
| [ws](https://github.com/websockets/ws) | `8.20.1` | `8.21.0` |


Updates `@aws-sdk/client-s3` from 3.1047.0 to 3.1056.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.1056.0/clients/client-s3)

Updates `@hookform/resolvers` from 5.2.2 to 5.4.0
- [Release notes](https://github.com/react-hook-form/resolvers/releases)
- [Commits](https://github.com/react-hook-form/resolvers/compare/v5.2.2...v5.4.0)

Updates `helmet` from 8.1.0 to 8.2.0
- [Changelog](https://github.com/helmetjs/helmet/blob/main/CHANGELOG.md)
- [Commits](https://github.com/helmetjs/helmet/compare/v8.1.0...v8.2.0)

Updates `ioredis` from 5.10.1 to 5.11.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.10.1...v5.11.0)

Updates `next-intl` from 4.12.0 to 4.13.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.12.0...v4.13.0)

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

Updates `posthog-node` from 5.34.1 to 5.35.6
- [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.35.6/packages/node)

Updates `react-hook-form` from 7.75.0 to 7.76.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.75.0...v7.76.1)

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

---
updated-dependencies:
- dependency-name: "@aws-sdk/client-s3"
  dependency-version: 3.1056.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: prod-minor-updates
- dependency-name: "@hookform/resolvers"
  dependency-version: 5.4.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: prod-minor-updates
- dependency-name: helmet
  dependency-version: 8.2.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: prod-minor-updates
- dependency-name: ioredis
  dependency-version: 5.11.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: prod-minor-updates
- dependency-name: next-intl
  dependency-version: 4.13.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: prod-minor-updates
- dependency-name: pg
  dependency-version: 8.21.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: prod-minor-updates
- dependency-name: posthog-node
  dependency-version: 5.35.6
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: prod-minor-updates
- dependency-name: react-hook-form
  dependency-version: 7.76.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: prod-minor-updates
- dependency-name: ws
  dependency-version: 8.21.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-05-29 01:43:25 +00:00
dependabot[bot]
18ac542e30 Bump the dev-patch-updates group with 4 updates
Bumps the dev-patch-updates group with 4 updates: [@tanstack/react-query-devtools](https://github.com/TanStack/query/tree/HEAD/packages/react-query-devtools), [@types/react](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/react), [postcss](https://github.com/postcss/postcss) and [tsx](https://github.com/privatenumber/tsx).


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

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

Updates `postcss` from 8.5.14 to 8.5.15
- [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.14...8.5.15)

Updates `tsx` from 4.22.0 to 4.22.3
- [Release notes](https://github.com/privatenumber/tsx/releases)
- [Changelog](https://github.com/privatenumber/tsx/blob/master/release.config.cjs)
- [Commits](https://github.com/privatenumber/tsx/compare/v4.22.0...v4.22.3)

---
updated-dependencies:
- dependency-name: "@tanstack/react-query-devtools"
  dependency-version: 5.100.14
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-patch-updates
- dependency-name: "@types/react"
  dependency-version: 19.2.15
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-patch-updates
- dependency-name: postcss
  dependency-version: 8.5.15
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-patch-updates
- dependency-name: tsx
  dependency-version: 4.22.3
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: dev-patch-updates
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-29 01:36:20 +00:00
Owen
322475fb5c Fix errors 2026-05-28 17:59:21 -07:00
Owen
2f124bffc4 Merge branch 'main' into dev 2026-05-28 17:46:42 -07:00
Owen Schwartz
86367383e7 Merge pull request #3026 from immanuwell/fix-validator-test-exit
fix: make validators test failures exit non-zero
2026-05-28 17:43:43 -07:00
Owen Schwartz
d22ba3566d Merge pull request #2618 from LunarECL/fix-docker-label-partial-validation
Skip invalid Docker resources instead of failing entire blueprint (#1784)
2026-05-28 17:41:15 -07:00
Owen Schwartz
c74b423bae Merge pull request #3119 from Adityakk9031/#3086
Sort resource filter options in audit logs
2026-05-28 15:50:27 -07:00
Owen
f8a757c55f Merge branch 'resource-policies' into dev 2026-05-28 15:30:16 -07:00
Owen
6aea3f1643 Merge branch 'auto-update' into dev 2026-05-28 13:59:34 -07:00
Owen
073dc34522 Merge branch 'rdp-ssh' into dev 2026-05-28 13:59:14 -07:00
Owen
3f5970a1f9 Merge branch 'dev' of github.com:fosrl/pangolin into dev 2026-05-28 13:56:18 -07:00
Owen
e2f2608358 Merge branch 'main' into dev 2026-05-28 13:56:08 -07:00
Owen Schwartz
6d17bb04c4 Merge pull request #3167 from shleeable/patch-1
Installer: format main.go
2026-05-28 12:13:45 -07:00
Owen Schwartz
957e7ba127 Merge pull request #3175 from shleeable/patch-4
Fix:  OLM token rate limit uses wrong field name
2026-05-28 12:13:04 -07:00
Owen Schwartz
def710cba8 Merge pull request #3176 from shleeable/patch-5
Fix: Update external.ts windowMs rate limit for milliseconds
2026-05-28 12:12:39 -07:00
Owen Schwartz
44da854575 Merge pull request #3177 from shleeable/patch-6
Fix: Missing return
2026-05-28 12:11:40 -07:00
miloschwartz
d3d2474855 update blueprints to say blueprints log 2026-05-28 12:11:20 -07:00
Owen Schwartz
d7d37c6f6e Merge pull request #3179 from fosrl/dependabot/npm_and_yarn/dev-minor-updates-545c73ecbb
Bump the dev-minor-updates group across 1 directory with 6 updates
2026-05-28 12:10:40 -07:00
dependabot[bot]
3c80b9a229 Bump the dev-minor-updates group across 1 directory with 6 updates
Bumps the dev-minor-updates group with 6 updates in the / directory:

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



Updates `@dotenvx/dotenvx` from 1.66.0 to 1.69.1
- [Release notes](https://github.com/dotenvx/dotenvx/releases)
- [Changelog](https://github.com/dotenvx/dotenvx/blob/main/CHANGELOG.md)
- [Commits](https://github.com/dotenvx/dotenvx/compare/v1.66.0...v1.69.1)

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

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

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

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

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

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

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

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



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

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

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

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

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

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

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

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

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

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

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

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

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

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

Adjust description
2026-05-21 16:34:32 -07:00
Fred KISSIE
09cb20a084 push 2026-05-22 00:44:29 +02:00
Owen
6d4afd0953 Control updates from the ui 2026-05-21 15:43:31 -07:00
Owen
d1fb2e19d3 Fix cache import to be dynamic 2026-05-21 14:43:50 -07:00
Owen
dee0ca6864 Add permissions check, shasum check, & build info 2026-05-21 14:34:16 -07:00
Fred KISSIE
2934bbdd20 ♻️ use react query for network logs 2026-05-21 23:27:02 +02:00
Fred KISSIE
2b46e8eaba ♻️ use useQuery for network action logs 2026-05-21 23:23:49 +02:00
Owen
ed73d089d0 Auto update newt 2026-05-21 14:13:32 -07:00
Owen
3b89104a59 Add regional redis cache 2026-05-21 14:07:09 -07:00
Fred KISSIE
5bf8b336c5 ♻️ useQuery for fetching access logs 2026-05-21 23:05:34 +02:00
Fred KISSIE
21a144753d Add access react query 2026-05-21 22:50:15 +02:00
Fred KISSIE
c1b8dfc863 ♻️ refactor 2026-05-21 22:44:24 +02:00
Fred KISSIE
5efcd4479a Merge branch 'dev' into refactor/loading-animation-on-request-logs 2026-05-21 22:31:16 +02:00
Owen
e4e8b33e9f Enforce absolute paths for sudo commands 2026-05-21 12:14:52 -07:00
Owen Schwartz
35ad235f49 Merge pull request #3129 from fosrl/fix-site-delete
Improve delete function speed & order of ops
2026-05-21 12:06:18 -07:00
Owen
834672c846 Improve delete function speed & order of ops 2026-05-21 12:05:16 -07:00
Owen
af13790c93 Fix pasting the device code not working 2026-05-20 16:28:12 -07:00
Owen Schwartz
b8180d848a Merge pull request #3118 from Adityakk9031/#3105
Fix public resource health with unknown WireGuard targets
2026-05-20 16:20:25 -07:00
Owen Schwartz
fef7563e14 Merge pull request #3125 from fosrl/fix-3104
Fix #3104
2026-05-20 16:15:21 -07:00
Owen
6337cf4359 Fix #3104 2026-05-20 16:14:47 -07:00
Owen
87bcd8ec1b Merge branch 'main' into dev 2026-05-20 15:59:01 -07:00
Owen Schwartz
b3cfe82dff Merge pull request #3124 from fosrl/fix-logoUrl
Fix logo url
2026-05-20 14:19:29 -07:00
Owen
d65128671c Fix logo url 2026-05-20 14:18:55 -07:00
Owen Schwartz
41fdd5de74 Merge pull request #3122 from fosrl/button-to-rebuild-association
Add button to rebuid cache
2026-05-20 12:08:47 -07:00
Owen
2704202ba9 Add button to rebuid cache 2026-05-20 12:08:20 -07:00
Owen Schwartz
72ef0ae020 Merge pull request #3121 from fosrl/patch-rebuild-sites
patch rebuild sites
2026-05-20 11:48:33 -07:00
Owen
1442faa740 Prevent concurrent rebuilds 2026-05-20 11:46:59 -07:00
Owen
6aa589e612 Block adds to clients in jit mode 2026-05-20 11:35:15 -07:00
Owen
4b1a8e14c4 Put long running into the background to end transaction 2026-05-20 11:18:47 -07:00
Owen
1a0db10b1a Verify button to verify cache 2026-05-20 11:15:15 -07:00
Owen
b7634086db Just accept any url for now 2026-05-20 10:47:37 -07:00
Aditya kumar singh
73e9e830c3 Sort resource filter options in audit logs 2026-05-20 11:13:50 +05:30
Aditya kumar singh
a6469e67a8 Fix public resource health with unknown WireGuard targets 2026-05-20 09:05:05 +05:30
Owen
23ca3efbf4 Merge branch 'dev' into rdp-ssh 2026-05-19 20:12:05 -07:00
Owen
0f9100fd3a Merge branch 'rdp-ssh' of github.com:fosrl/pangolin into rdp-ssh 2026-05-19 20:06:48 -07:00
Owen Schwartz
c47c411161 Merge pull request #3114 from Fredkiss3/fix/tag-input-scroll
fix: make tag input wrap around instead of scrolling
2026-05-19 20:03:59 -07:00
Owen Schwartz
e88e262abe Merge pull request #3004 from Fredkiss3/feat/labels-on-sites-and-resources
feat: site & resource labels
2026-05-19 20:03:22 -07:00
Owen
832d45e32b Move pages back 2026-05-19 20:02:27 -07:00
Owen
69e3ac3cd4 Move login page locations 2026-05-19 20:02:27 -07:00
Owen
50865f4265 Remove terminate button 2026-05-19 20:02:27 -07:00
Owen
0d1a8d9695 Only switch if we are actually connected 2026-05-19 20:02:27 -07:00
Owen
5d8486dd7f Sure up some things with browserAccessType 2026-05-19 20:02:27 -07:00
Owen
3c25932787 Adjust page to be editable 2026-05-19 20:02:27 -07:00
Owen
1d0e1eb126 Temp credential storage 2026-05-19 20:02:27 -07:00
Owen
57c0dc8618 Support private key 2026-05-19 20:02:27 -07:00
Owen
526a147570 Clean up toasts 2026-05-19 20:02:27 -07:00
Owen
0938997548 Add crud for browser targets 2026-05-19 20:02:27 -07:00
Owen
0876b482f8 Remove extra fields 2026-05-19 20:02:27 -07:00
Owen
d558c31f88 Standardize the ui 2026-05-19 20:02:26 -07:00
Owen
6010515da0 Pull in the destination from the api 2026-05-19 20:02:26 -07:00
Owen
868bcd8e34 USe right table 2026-05-19 20:02:26 -07:00
Owen
20c4904965 Add internal api get for proxy information 2026-05-19 20:02:26 -07:00
Owen
5a5536b38c Reinstall packages 2026-05-19 20:02:26 -07:00
Owen
53e2296de8 Clean up forms a bit 2026-05-19 20:02:26 -07:00
Owen
d2423919e9 Add favicon passthrough 2026-05-19 20:02:26 -07:00
Owen
2250fcd177 Serve the resource from the right place 2026-05-19 20:02:26 -07:00
Owen
2a33256d17 Add gateway endpoints into the traefik config 2026-05-19 20:02:26 -07:00
Owen
117aa750f8 Working on new target type 2026-05-19 20:02:26 -07:00
Owen
15f161274f Add browserGatewayTarget table 2026-05-19 20:02:26 -07:00
Owen
09779aca3e Add basic vnc test 2026-05-19 20:02:25 -07:00
Owen
1d1f7cecf4 Support rdp 2026-05-19 20:02:25 -07:00
Owen
dc00668cbe Add first iteration of ssh proxy 2026-05-19 20:02:25 -07:00
Owen
57701e13eb Comment out some fields 2026-05-19 20:02:25 -07:00
Owen
46545cb003 Initial rdp working 2026-05-19 20:02:21 -07:00
Fred KISSIE
a163cc3678 💄 show loading animation on http request logs table 2026-05-20 04:50:49 +02:00
Fred KISSIE
1dfb3408e8 ♻️ use react query for fetching instead of useEfffect 2026-05-20 01:00:56 +02:00
Fred KISSIE
67fb2beba1 Merge branch 'dev' into refactor/loading-animation-on-request-logs 2026-05-19 23:54:21 +02:00
Fred KISSIE
6cacc9b83f 💄 limit tag width 2026-05-19 22:52:44 +02:00
Fred KISSIE
1f1791feb7 💄 make tag input wrap around instead of scrolling 2026-05-19 22:48:15 +02:00
Owen Schwartz
1ba75092f9 Merge pull request #3113 from fosrl/dev
derived only from roles that the user holds AND are assigned to the target resource
2026-05-19 10:56:30 -07:00
Owen
08a08e73b3 derived only from roles that the user holds AND are assigned to the target resource 2026-05-19 10:53:54 -07:00
Fred KISSIE
c500979099 Merge branch 'dev' into refactor/loading-animation-on-request-logs 2026-05-18 22:52:27 +02:00
Fred KISSIE
2d9c082607 💄 UI 2026-05-18 22:17:49 +02:00
Fred KISSIE
7968c4357b edit org label 2026-05-18 22:14:49 +02:00
Fred KISSIE
25c08e7279 Create label dialog 2026-05-18 21:57:44 +02:00
copilot-swe-agent[bot]
81ed391efb Remove obsolete stoi import from blueprint OpenAPI route
Agent-Logs-Url: https://github.com/fosrl/pangolin/sessions/7b395a8e-7fae-4f4d-952e-4030fea08262

Co-authored-by: oschwartz10612 <4999704+oschwartz10612@users.noreply.github.com>
2026-05-17 21:30:23 +00:00
copilot-swe-agent[bot]
f3bee70c23 Reformat generated OpenAPI response schemas for readability
Agent-Logs-Url: https://github.com/fosrl/pangolin/sessions/7b395a8e-7fae-4f4d-952e-4030fea08262

Co-authored-by: oschwartz10612 <4999704+oschwartz10612@users.noreply.github.com>
2026-05-17 21:28:39 +00:00
copilot-swe-agent[bot]
15a9eb28d9 Add concrete OpenAPI data schemas for selected routes
Agent-Logs-Url: https://github.com/fosrl/pangolin/sessions/7b395a8e-7fae-4f4d-952e-4030fea08262

Co-authored-by: oschwartz10612 <4999704+oschwartz10612@users.noreply.github.com>
2026-05-17 21:25:53 +00:00
copilot-swe-agent[bot]
a0a093ed0b Document all registerPath responses and normalize OpenAPI parser schemas
Agent-Logs-Url: https://github.com/fosrl/pangolin/sessions/73990123-9c27-444b-bc6e-77e890a0d57c

Co-authored-by: oschwartz10612 <4999704+oschwartz10612@users.noreply.github.com>
2026-05-17 06:43:10 +00:00
copilot-swe-agent[bot]
9cec711427 Fix custom parser OpenAPI types and add structured default response schema
Agent-Logs-Url: https://github.com/fosrl/pangolin/sessions/73990123-9c27-444b-bc6e-77e890a0d57c

Co-authored-by: oschwartz10612 <4999704+oschwartz10612@users.noreply.github.com>
2026-05-17 06:38:44 +00:00
Owen Schwartz
82745c701a Merge pull request #3094 from fosrl/dev
Sync dev
2026-05-16 20:46:12 -07:00
Owen
68e775659b Merge branch 'main' into dev 2026-05-16 20:45:39 -07:00
Owen
1c5e3000b6 Merge branch 'dev' of github.com:fosrl/pangolin into dev 2026-05-16 20:45:31 -07:00
Owen
3b93fd99a1 Remove workflows 2026-05-16 20:44:36 -07:00
dependabot[bot]
e4fd2b656d Bump sigstore/cosign-installer from 4.1.1 to 4.1.2
Bumps [sigstore/cosign-installer](https://github.com/sigstore/cosign-installer) from 4.1.1 to 4.1.2.
- [Release notes](https://github.com/sigstore/cosign-installer/releases)
- [Commits](https://github.com/sigstore/cosign-installer/compare/v4.1.1...6f9f17788090df1f26f669e9d70d6ae9567deba6)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-16 21:55:15 +00:00
Owen Schwartz
159e91a07c Merge pull request #3090 from fosrl/github-action-cosign
Upgrade cosign installer to v4.1.2 and pin cosign version
2026-05-16 14:53:24 -07:00
miloschwartz
530b5082bd make online/connected styling consistent 2026-05-16 12:34:17 -07:00
Marc Schäfer
3322f1ccb4 Update cosign installer version in CI workflow 2026-05-16 16:21:13 +02:00
Marc Schäfer
1b17fba19f Upgrade cosign installer to v4.1.2 and pin cosign version
Updated cosign installer to version 4.1.2 and specified cosign release version.
2026-05-16 16:17:45 +02:00
Owen
987b5d580e Sure up some things with browserAccessType 2026-05-15 17:26:58 -07:00
Owen
cb75ffc3b7 Adjust page to be editable 2026-05-15 16:46:43 -07:00
Owen
540f0a754d Temp credential storage 2026-05-15 16:11:23 -07:00
Owen
0f9a6fd968 Support private key 2026-05-15 16:07:14 -07:00
Owen
82112abc34 Clean up toasts 2026-05-15 15:01:37 -07:00
Owen
75b5afd544 Add crud for browser targets 2026-05-15 14:09:33 -07:00
Owen
00e1675f7b Remove extra fields 2026-05-15 12:08:40 -07:00
Owen
2ddbdf977b Standardize the ui 2026-05-15 12:06:05 -07:00
Owen
4c8f0cc9ec Pull in the destination from the api 2026-05-15 11:48:13 -07:00
Marc Schäfer
18d380ce30 fix(security): normalize request parameters and update dependencies
Signed-off-by: Marc Schäfer <git@marcschaeferger.de>
2026-05-15 18:35:58 +00:00
Owen
e822b681cd Merge branch 'dev' into rdp-ssh 2026-05-15 11:18:31 -07:00
Owen
dd1f7ba544 Make crowdsec --crowdsec 2026-05-14 21:46:26 -07:00
Owen Schwartz
8c2e6965f1 Merge pull request #3081 from fosrl/dev
Update sidebar
2026-05-14 21:21:03 -07:00
Owen
b414f04cce Remove funding 2026-05-14 21:20:34 -07:00
Owen Schwartz
9c71922dda Merge pull request #3079 from fosrl/dev
Add site information to user api
2026-05-14 20:17:19 -07:00
Owen
6e4a28f227 Add site information as well 2026-05-14 18:02:42 -07:00
Owen Schwartz
64d8f035a2 Merge pull request #3077 from fosrl/dev
1.18.4-s.5
2026-05-14 17:41:51 -07:00
Owen
0a5780a3b3 Merge branch 'dev' of github.com:fosrl/pangolin into dev 2026-05-14 17:41:00 -07:00
Owen
d58b96f4b1 Add port and icmp information to api endpoint 2026-05-14 17:39:22 -07:00
Owen Schwartz
f778f5c941 Merge pull request #3071 from fosrl/crowdin_dev
New Crowdin updates
2026-05-14 17:20:50 -07:00
Owen
6422208f69 Optimize get all relays 2026-05-14 16:59:15 -07:00
Owen
c3ebc423b5 Each node should only update its own sites 2026-05-14 16:51:09 -07:00
Fred KISSIE
68d7b0a416 🚧 wip: label 2026-05-14 22:43:29 +02:00
Fred KISSIE
43546c84eb 🚧 wip: create label dialog 2026-05-14 22:42:01 +02:00
Fred KISSIE
eac36ee442 delete label 2026-05-14 22:15:43 +02:00
Owen Schwartz
92f992728f Merge pull request #3074 from fosrl/dev
Optimize building aliases in jit mode
2026-05-14 12:25:44 -07:00
Owen
78ad2d17c7 Optimize building aliases in jit mode 2026-05-14 12:25:05 -07:00
Fred KISSIE
9a88394efe 🛂 gate label endpoints behing subscription 2026-05-14 21:17:58 +02:00
Fred KISSIE
173562654b delete org label endpoint 2026-05-14 21:09:48 +02:00
Owen Schwartz
b29bb7384d Merge pull request #3073 from fosrl/dev
Further optimizations
2026-05-14 12:00:25 -07:00
Owen
5a8de8210b Further optimizations 2026-05-14 11:59:59 -07:00
Owen Schwartz
d5181454f4 Merge pull request #3072 from fosrl/dev
Optimize this
2026-05-14 11:34:56 -07:00
Owen
0e0666cacf Optimize this 2026-05-14 11:34:09 -07:00
Owen
e1583a58aa USe right table 2026-05-14 11:33:42 -07:00
Owen Schwartz
02ba2393b9 New translations en-us.json (German)
[ci skip]
2026-05-14 11:08:12 -07:00
Fred KISSIE
8f7e5ab1ed 🚧 wip: org labels page 2026-05-14 19:31:53 +02:00
Fred KISSIE
4334480675 ♻️ refactor 2026-05-14 18:33:29 +02:00
Fred KISSIE
6aa406927a 🐛 fix error message 2026-05-14 18:20:26 +02:00
Fred KISSIE
5b50024712 Merge branch 'dev' into feat/labels-on-sites-and-resources 2026-05-14 18:15:14 +02:00
Owen
7d922ac95f Add internal api get for proxy information 2026-05-13 21:54:58 -07:00
Owen
795a3d351e Reinstall packages 2026-05-13 21:16:40 -07:00
Owen
4b4c86b4b7 Clean up forms a bit 2026-05-13 21:16:00 -07:00
Owen
013af49137 Add favicon passthrough 2026-05-13 21:11:25 -07:00
Owen
a6ae9290f2 Serve the resource from the right place 2026-05-13 18:01:36 -07:00
Owen
de70d72e0d Add gateway endpoints into the traefik config 2026-05-13 17:33:16 -07:00
Owen Schwartz
daf260cf61 Merge pull request #3064 from fosrl/dev
1.18.4-s.1
2026-05-13 14:40:50 -07:00
Owen
92a06e0ea3 Handle jit mode with syncs 2026-05-13 14:00:43 -07:00
Owen
c16d2ff2ed Fix log message 2026-05-13 13:52:35 -07:00
Owen
73a4d7d351 Quiet log message 2026-05-13 11:57:02 -07:00
Owen
4e07e9c52c Working on new target type 2026-05-13 11:56:23 -07:00
Owen
743621eb25 Add browserGatewayTarget table 2026-05-12 21:48:59 -07:00
Owen
e9df995e76 Merge branch 'dev' into resource-policies 2026-05-12 21:12:40 -07:00
Owen
943923ff4b Add basic vnc test 2026-05-12 21:12:01 -07:00
Owen
3f17f1a468 Support rdp 2026-05-12 21:12:01 -07:00
Owen
436996a43d Add first iteration of ssh proxy 2026-05-12 21:12:01 -07:00
Owen
d42b6076d2 Comment out some fields 2026-05-12 21:12:01 -07:00
Owen
89cc99f915 Initial rdp working 2026-05-12 21:12:00 -07:00
Owen Schwartz
1860b4b862 Merge pull request #3061 from fosrl/dev
Add 1.18.4 migration
2026-05-12 21:01:39 -07:00
Owen
efb1d69ac9 Add migration 2026-05-12 20:59:58 -07:00
Owen Schwartz
0601b55f22 Merge pull request #3060 from fosrl/dev
Translations
2026-05-12 20:48:55 -07:00
Owen Schwartz
107986d848 Merge pull request #3059 from fosrl/crowdin_dev
New Crowdin updates
2026-05-12 20:45:53 -07:00
Owen Schwartz
b6c8fbe43b New translations en-us.json (Spanish)
[ci skip]
2026-05-12 20:41:30 -07:00
Owen Schwartz
4208a9f372 New translations en-us.json (Norwegian Bokmal)
[ci skip]
2026-05-12 20:41:28 -07:00
Owen Schwartz
3c82a228fb New translations en-us.json (Chinese Simplified)
[ci skip]
2026-05-12 20:41:26 -07:00
Owen Schwartz
a4aa29e48a New translations en-us.json (Turkish)
[ci skip]
2026-05-12 20:41:25 -07:00
Owen Schwartz
0f82ba6627 New translations en-us.json (Russian)
[ci skip]
2026-05-12 20:41:23 -07:00
Owen Schwartz
1df5d9fac8 New translations en-us.json (Portuguese)
[ci skip]
2026-05-12 20:41:21 -07:00
Owen Schwartz
5189583d73 New translations en-us.json (Polish)
[ci skip]
2026-05-12 20:41:20 -07:00
Owen Schwartz
b794d2aa40 New translations en-us.json (Dutch)
[ci skip]
2026-05-12 20:41:18 -07:00
Owen Schwartz
c69059b227 New translations en-us.json (Korean)
[ci skip]
2026-05-12 20:41:17 -07:00
Owen Schwartz
b27b62d4c8 New translations en-us.json (Italian)
[ci skip]
2026-05-12 20:41:15 -07:00
Owen Schwartz
ee8290d68c New translations en-us.json (German)
[ci skip]
2026-05-12 20:41:13 -07:00
Owen Schwartz
82e8e79b16 New translations en-us.json (Czech)
[ci skip]
2026-05-12 20:41:12 -07:00
Owen Schwartz
2d428d2fa0 New translations en-us.json (Bulgarian)
[ci skip]
2026-05-12 20:41:10 -07:00
Owen Schwartz
0005c11a0a New translations en-us.json (French)
[ci skip]
2026-05-12 20:41:08 -07:00
Owen Schwartz
559cbeb7d5 Merge pull request #3058 from fosrl/dev
1.18.4
2026-05-12 20:20:10 -07:00
Owen
f91d914ec6 Show when a domain is config managed 2026-05-12 20:14:12 -07:00
Owen Schwartz
e975f56445 Merge pull request #3008 from AstralDestiny/patch-2
Update references of SSL alone to be TLS to align with proper wording…
2026-05-12 13:57:11 -07:00
Fred KISSIE
ce746a2a21 Handle labels for machine clients 2026-05-12 22:32:56 +02:00
Fred KISSIE
7120ab4b22 ♻️ filter sites & resources by labels 2026-05-12 20:45:12 +02:00
Fred KISSIE
12e777b32e Add labels column to private resources table 2026-05-12 20:25:32 +02:00
Fred KISSIE
9378103ddd handle private resources filtering by labels 2026-05-12 20:24:34 +02:00
Fred KISSIE
ec794d5de2 attach/detach private resources 2026-05-12 20:01:33 +02:00
Fred KISSIE
12b18a3e8c attach labels to private resources 2026-05-12 19:58:44 +02:00
Fred KISSIE
91e8a13e59 🗃️ Add site resource labels schema 2026-05-12 17:55:56 +02:00
Fred KISSIE
931ba0f540 💄 px-2 button 2026-05-12 17:46:46 +02:00
miloschwartz
b6caeda0a5 improve targets round robin warning 2026-05-11 22:06:43 -07:00
Owen
77d17af15b Add global hide_powered_by and make it backward 2026-05-11 16:18:57 -07:00
Owen
264c6bf4e8 Use the right param for user 2026-05-11 12:06:36 -07:00
Fred KISSIE
d321d7275c 🚧 tried to memo proxy resource table, failed 2026-05-11 21:06:20 +02:00
Owen
4aa72eb1a3 Confirm delete of share links 2026-05-11 11:49:51 -07:00
Owen
a066a68e1a Pick the most specific domain
Fixes #3047
2026-05-11 11:28:32 -07:00
Fred KISSIE
3855486a00 ️ prevent SitetableCell from rerendering unnecessarily 2026-05-11 19:27:00 +02:00
Fred KISSIE
ab494521b1 labels on proxy resources 2026-05-11 18:37:16 +02:00
Fred KISSIE
549e1ead1d handle labels in resources too 2026-05-11 18:30:23 +02:00
Fred KISSIE
a0759a79a1 🗃️ add unique indexes to site & resource labels in sqlite 2026-05-11 18:28:40 +02:00
Fred KISSIE
14e1a119d3 🚧 WIP: showing labels in proxy resources table 2026-05-11 18:24:47 +02:00
Fred KISSIE
6e066d38b0 🚚 Make label badge its own component 2026-05-11 18:17:29 +02:00
Fred KISSIE
21f72639b6 🚧 make labels column paid, and cleanup 2026-05-11 18:13:19 +02:00
Fred KISSIE
8a0c2031d4 search list by labels too 2026-05-11 18:02:59 +02:00
Fred KISSIE
56d3a466e5 💄 make controlled data table input a search input 2026-05-11 18:02:44 +02:00
Fred KISSIE
563e505cc1 💸 add labels to paid features 2026-05-11 18:02:15 +02:00
Fred KISSIE
c44c02b8ba 💄 make site labels column design nicer 2026-05-11 17:04:44 +02:00
Fred KISSIE
b9ab35a05b 🐛 handle idempotency when adding/removing labels from sites/resources 2026-05-11 16:57:53 +02:00
miloschwartz
9fb677e952 allow editing self and owner user roles 2026-05-08 17:48:43 -07:00
Owen Schwartz
e253195fdd Merge pull request #3035 from fosrl/dev
Add new log streaming and client endpoint to connection log
2026-05-08 17:18:16 -07:00
Owen Schwartz
88d8414eb8 Merge pull request #3016 from fosrl/crowdin_dev
New Crowdin updates
2026-05-08 17:17:30 -07:00
Owen Schwartz
5f3fafb1b0 New translations en-us.json (Spanish)
[ci skip]
2026-05-08 17:16:31 -07:00
Owen Schwartz
de1338a8cd New translations en-us.json (Norwegian Bokmal)
[ci skip]
2026-05-08 17:16:29 -07:00
Owen Schwartz
0800aa2a61 New translations en-us.json (Chinese Simplified)
[ci skip]
2026-05-08 17:16:28 -07:00
Owen Schwartz
4959d66ac1 New translations en-us.json (Turkish)
[ci skip]
2026-05-08 17:16:26 -07:00
Owen Schwartz
9320df8be6 New translations en-us.json (Russian)
[ci skip]
2026-05-08 17:16:24 -07:00
Owen Schwartz
13ec6b6620 New translations en-us.json (Portuguese)
[ci skip]
2026-05-08 17:16:23 -07:00
Owen Schwartz
2ca3ef019c New translations en-us.json (Polish)
[ci skip]
2026-05-08 17:16:21 -07:00
Owen Schwartz
724e41a54f New translations en-us.json (Dutch)
[ci skip]
2026-05-08 17:16:19 -07:00
Owen Schwartz
ce5e62d216 New translations en-us.json (Korean)
[ci skip]
2026-05-08 17:16:17 -07:00
Owen Schwartz
874dc2b33e New translations en-us.json (Italian)
[ci skip]
2026-05-08 17:16:16 -07:00
Owen Schwartz
3b2622d590 New translations en-us.json (German)
[ci skip]
2026-05-08 17:16:14 -07:00
Owen Schwartz
c81d855741 New translations en-us.json (Czech)
[ci skip]
2026-05-08 17:16:12 -07:00
Owen Schwartz
3bce8d3596 New translations en-us.json (Bulgarian)
[ci skip]
2026-05-08 17:16:10 -07:00
Owen Schwartz
ee2a1e2bc3 New translations en-us.json (French)
[ci skip]
2026-05-08 17:16:09 -07:00
Owen Schwartz
a0f3ee74f9 New translations en-us.json (Spanish)
[ci skip]
2026-05-08 17:14:09 -07:00
Owen Schwartz
82a36fd632 New translations en-us.json (Norwegian Bokmal)
[ci skip]
2026-05-08 17:14:07 -07:00
Owen Schwartz
c5084137ab New translations en-us.json (Chinese Simplified)
[ci skip]
2026-05-08 17:14:06 -07:00
Owen Schwartz
65ec8da100 New translations en-us.json (Turkish)
[ci skip]
2026-05-08 17:14:04 -07:00
Owen Schwartz
e76e7581a5 New translations en-us.json (Russian)
[ci skip]
2026-05-08 17:14:02 -07:00
Owen Schwartz
a97a4b6ec1 New translations en-us.json (Portuguese)
[ci skip]
2026-05-08 17:14:00 -07:00
Owen Schwartz
e38bbde348 New translations en-us.json (Polish)
[ci skip]
2026-05-08 17:13:58 -07:00
Owen Schwartz
026260ddfb New translations en-us.json (Dutch)
[ci skip]
2026-05-08 17:13:57 -07:00
Owen Schwartz
97be5eb7d5 New translations en-us.json (Korean)
[ci skip]
2026-05-08 17:13:55 -07:00
Owen Schwartz
d7b96ba3f5 New translations en-us.json (Italian)
[ci skip]
2026-05-08 17:13:53 -07:00
Owen Schwartz
b42672530f New translations en-us.json (German)
[ci skip]
2026-05-08 17:13:51 -07:00
Owen Schwartz
b6b2dbd8ab New translations en-us.json (Czech)
[ci skip]
2026-05-08 17:13:50 -07:00
Owen Schwartz
975f3a01f5 New translations en-us.json (Bulgarian)
[ci skip]
2026-05-08 17:13:48 -07:00
Owen Schwartz
4de2dfff85 New translations en-us.json (French)
[ci skip]
2026-05-08 17:13:46 -07:00
Owen
27d230647f Merge branch 's3' into dev 2026-05-08 17:05:39 -07:00
Owen
114486608e Add client endpoint to network log 2026-05-08 17:04:58 -07:00
Owen
10fa9274d0 Add streaming errors for debug 2026-05-08 16:27:40 -07:00
Fred KISSIE
2fd519e102 add and toggle site labels 2026-05-08 22:31:36 +02:00
Fred KISSIE
a63c1ec364 💄 label selector (with create label) 2026-05-08 21:49:20 +02:00
Fred KISSIE
e61ef2ca2a 🚧 wip: label selector 2026-05-08 20:06:42 +02:00
Fred KISSIE
39b09b7f3f Merge branch 'dev' into feat/labels-on-sites-and-resources 2026-05-08 18:21:46 +02:00
Fred KISSIE
840cc214e3 🚧 wip 2026-05-08 18:21:09 +02:00
Owen
cbdc74768f Implement s3 streaming destination 2026-05-07 21:09:21 -07:00
Owen Schwartz
10f95896aa Merge pull request #3030 from fosrl/dev
1.18.3-s.2 fix
2026-05-07 20:08:05 -07:00
Owen
5b8994d143 Cange to use primaryDb 2026-05-07 20:07:06 -07:00
Owen
c46ef2fe9c Fix ts type issue 2026-05-07 20:03:48 -07:00
Fred KISSIE
72524db52d 💄 shrink button 2026-05-08 02:48:47 +02:00
Fred KISSIE
ab8fc11ab3 🚧 add labels button 2026-05-08 02:46:16 +02:00
Owen Schwartz
4cd025dd91 Merge pull request #3029 from fosrl/dev
1.18.3-s.2
2026-05-07 17:44:35 -07:00
Owen
ce04ea9720 Fix not including today
Fixes #3028
2026-05-07 16:15:13 -07:00
Owen
a3ce382725 Pick up other domains in the sans field 2026-05-07 15:49:12 -07:00
Owen
4eb49e3e60 Make the rebuild long running function background 2026-05-07 15:40:34 -07:00
Fred KISSIE
1831ca4e75 ♻️ detach label from site/resoirce 2026-05-08 00:33:47 +02:00
Owen
2a9481023a Dont show link when wildcard 2026-05-07 15:15:03 -07:00
Owen
8ed01372b8 Add org to logs 2026-05-07 15:14:44 -07:00
immanuwell
0611ceb5c3 fix: make validators test failures exit non-zero 2026-05-07 20:13:56 +04:00
Owen Schwartz
6a7d4fd385 Merge pull request #3021 from fosrl/dev
If not exists on trial table
2026-05-06 20:00:55 -07:00
Owen
7bc08c0425 If not exists on trial table 2026-05-06 20:00:23 -07:00
Owen Schwartz
451f3d24a8 New translations en-us.json (French)
[ci skip]
2026-05-06 17:13:33 -07:00
Owen Schwartz
36a47c4cfb Merge pull request #3015 from fosrl/dev
Dev
2026-05-06 16:59:02 -07:00
Owen
7dce4500ec Merge branch 'dev' of github.com:fosrl/pangolin into dev 2026-05-06 16:58:39 -07:00
Owen
72e48a56df Remove explicit call 2026-05-06 16:58:28 -07:00
Owen Schwartz
293d9865b4 Merge pull request #3014 from fosrl/dev
1.18.3
2026-05-06 16:30:36 -07:00
Owen Schwartz
45a2a07747 Merge pull request #3012 from fosrl/crowdin_dev
New Crowdin updates
2026-05-06 16:21:45 -07:00
Owen Schwartz
181bcffe7d New translations en-us.json (Spanish)
[ci skip]
2026-05-06 16:17:46 -07:00
Owen Schwartz
ed35d25598 New translations en-us.json (Norwegian Bokmal)
[ci skip]
2026-05-06 16:17:44 -07:00
Owen Schwartz
05e738e0f4 New translations en-us.json (Chinese Simplified)
[ci skip]
2026-05-06 16:17:42 -07:00
Owen Schwartz
c95e66d531 New translations en-us.json (Turkish)
[ci skip]
2026-05-06 16:17:40 -07:00
Owen Schwartz
cc2a416a92 New translations en-us.json (Russian)
[ci skip]
2026-05-06 16:17:39 -07:00
Owen Schwartz
70bb42f1fc New translations en-us.json (Portuguese)
[ci skip]
2026-05-06 16:17:37 -07:00
Owen Schwartz
10d2bc1e9e New translations en-us.json (Polish)
[ci skip]
2026-05-06 16:17:35 -07:00
Owen Schwartz
385f57ec93 New translations en-us.json (Dutch)
[ci skip]
2026-05-06 16:17:33 -07:00
Owen Schwartz
9c8ffdb661 New translations en-us.json (Korean)
[ci skip]
2026-05-06 16:17:32 -07:00
Owen Schwartz
5a5feccc76 New translations en-us.json (Italian)
[ci skip]
2026-05-06 16:17:30 -07:00
Owen Schwartz
36e7054386 New translations en-us.json (German)
[ci skip]
2026-05-06 16:17:28 -07:00
Owen Schwartz
19de12b12e New translations en-us.json (Czech)
[ci skip]
2026-05-06 16:17:26 -07:00
Owen Schwartz
d96e930679 New translations en-us.json (Bulgarian)
[ci skip]
2026-05-06 16:17:25 -07:00
Owen Schwartz
5e51b8ad74 New translations en-us.json (French)
[ci skip]
2026-05-06 16:17:23 -07:00
Owen Schwartz
885b9e638d New translations en-us.json (Spanish)
[ci skip]
2026-05-06 16:15:56 -07:00
Owen Schwartz
56ef3a934a New translations en-us.json (Norwegian Bokmal)
[ci skip]
2026-05-06 16:15:55 -07:00
Owen Schwartz
98bc199c8e New translations en-us.json (Chinese Simplified)
[ci skip]
2026-05-06 16:15:53 -07:00
Owen Schwartz
0444d3490b New translations en-us.json (Turkish)
[ci skip]
2026-05-06 16:15:51 -07:00
Owen Schwartz
54820d1db0 New translations en-us.json (Russian)
[ci skip]
2026-05-06 16:15:49 -07:00
Owen Schwartz
961cbfcacc New translations en-us.json (Portuguese)
[ci skip]
2026-05-06 16:15:47 -07:00
Owen Schwartz
a784cd307e New translations en-us.json (Polish)
[ci skip]
2026-05-06 16:15:46 -07:00
Owen Schwartz
b46c948522 New translations en-us.json (Dutch)
[ci skip]
2026-05-06 16:15:44 -07:00
Owen Schwartz
7eab2cc0bb New translations en-us.json (Korean)
[ci skip]
2026-05-06 16:15:42 -07:00
Owen Schwartz
5ff2569ece New translations en-us.json (Italian)
[ci skip]
2026-05-06 16:15:40 -07:00
Owen Schwartz
c59505be8d New translations en-us.json (German)
[ci skip]
2026-05-06 16:15:38 -07:00
Owen Schwartz
2b0e6649fa New translations en-us.json (Czech)
[ci skip]
2026-05-06 16:15:37 -07:00
Owen Schwartz
428e9b546e New translations en-us.json (Bulgarian)
[ci skip]
2026-05-06 16:15:35 -07:00
Owen Schwartz
5089660381 New translations en-us.json (French)
[ci skip]
2026-05-06 16:15:33 -07:00
Owen
998364b09d Properly respect flags.disableEnterpriseFeatures 2026-05-06 16:13:07 -07:00
Owen
ac0d88d9b7 Merge branch 'dev' of github.com:fosrl/pangolin into dev 2026-05-06 16:02:31 -07:00
Owen Schwartz
401f04b53e Merge pull request #3011 from fosrl/copilot/fix-create-alert-visibility
Hide alerting UI when disable_enterprise_features is true
2026-05-06 16:02:22 -07:00
Owen
b046ab7513 Add locks to allocations 2026-05-06 15:58:51 -07:00
Owen
65ee9b9544 Add transaction to alias address picking 2026-05-06 15:53:46 -07:00
Owen
49c7319342 Format and make the error a warning 2026-05-06 15:51:05 -07:00
Owen
ce7df5ddaa Update log message 2026-05-06 15:19:13 -07:00
Owen
af1739fbcb Bump version 2026-05-06 15:15:03 -07:00
Owen
f01c9ee41c Try to fix time issue
Fixes #3007
2026-05-06 14:45:18 -07:00
Owen
19f8956218 Support flattened data fields 2026-05-06 14:30:57 -07:00
Owen
a8c50b8618 Add clear certificates pangctl command 2026-05-06 14:08:28 -07:00
Owen
e86a381ed5 Fix the input to be tags 2026-05-06 14:05:18 -07:00
Owen
dd18375f23 Fix org selectors 2026-05-06 13:57:17 -07:00
Owen Schwartz
46b72b9e8c New translations en-us.json (Spanish)
[ci skip]
2026-05-06 11:14:54 -07:00
Owen Schwartz
7bb2a5a0a5 New translations en-us.json (Norwegian Bokmal)
[ci skip]
2026-05-06 11:14:52 -07:00
Owen Schwartz
4b777b1488 New translations en-us.json (Chinese Simplified)
[ci skip]
2026-05-06 11:14:50 -07:00
Owen Schwartz
428f91b5fa New translations en-us.json (Turkish)
[ci skip]
2026-05-06 11:14:48 -07:00
Owen Schwartz
caaae77f74 New translations en-us.json (Russian)
[ci skip]
2026-05-06 11:14:46 -07:00
Owen Schwartz
4df27b316c New translations en-us.json (Portuguese)
[ci skip]
2026-05-06 11:14:43 -07:00
Owen Schwartz
8f52a48937 New translations en-us.json (Polish)
[ci skip]
2026-05-06 11:14:41 -07:00
Owen Schwartz
a53da85fb4 New translations en-us.json (Dutch)
[ci skip]
2026-05-06 11:14:39 -07:00
Owen Schwartz
08a5785cc5 New translations en-us.json (Korean)
[ci skip]
2026-05-06 11:14:37 -07:00
Owen Schwartz
ff928b846d New translations en-us.json (Italian)
[ci skip]
2026-05-06 11:14:35 -07:00
Owen Schwartz
47b3d26d0e New translations en-us.json (German)
[ci skip]
2026-05-06 11:14:32 -07:00
Owen Schwartz
6270dce86a New translations en-us.json (Czech)
[ci skip]
2026-05-06 11:14:30 -07:00
Owen Schwartz
864d1d5cc4 New translations en-us.json (Bulgarian)
[ci skip]
2026-05-06 11:14:28 -07:00
Owen Schwartz
b63eda64f4 New translations en-us.json (French)
[ci skip]
2026-05-06 11:14:26 -07:00
Owen Schwartz
b8e942478d New translations en-us.json (Spanish)
[ci skip]
2026-05-06 11:09:41 -07:00
Owen Schwartz
6d9bfbf08f New translations en-us.json (Norwegian Bokmal)
[ci skip]
2026-05-06 11:09:39 -07:00
Owen Schwartz
35ce947e19 New translations en-us.json (Chinese Simplified)
[ci skip]
2026-05-06 11:09:37 -07:00
Owen Schwartz
b17ba96235 New translations en-us.json (Turkish)
[ci skip]
2026-05-06 11:09:35 -07:00
Owen Schwartz
f1bdb25497 New translations en-us.json (Russian)
[ci skip]
2026-05-06 11:09:33 -07:00
Owen Schwartz
e11527b430 New translations en-us.json (Portuguese)
[ci skip]
2026-05-06 11:09:31 -07:00
Owen Schwartz
31d3b314e9 New translations en-us.json (Polish)
[ci skip]
2026-05-06 11:09:29 -07:00
Owen Schwartz
3bce57c65c New translations en-us.json (Dutch)
[ci skip]
2026-05-06 11:09:27 -07:00
Owen Schwartz
d649a83535 New translations en-us.json (Korean)
[ci skip]
2026-05-06 11:09:25 -07:00
Owen Schwartz
3c6b1781bc New translations en-us.json (Italian)
[ci skip]
2026-05-06 11:09:23 -07:00
Owen Schwartz
7dd50f65fc New translations en-us.json (German)
[ci skip]
2026-05-06 11:09:20 -07:00
Owen Schwartz
342b4aeddf New translations en-us.json (Czech)
[ci skip]
2026-05-06 11:09:18 -07:00
Owen Schwartz
65908fa00f New translations en-us.json (Bulgarian)
[ci skip]
2026-05-06 11:09:16 -07:00
Owen Schwartz
223e0d0706 New translations en-us.json (French)
[ci skip]
2026-05-06 11:09:14 -07:00
Owen
5426031cd4 Remove duplicate ssl toggle 2026-05-06 11:05:08 -07:00
Owen
adf4a1ffda Link to http private resources 2026-05-06 11:03:38 -07:00
Owen
780feba19c Translate the member page 2026-05-06 10:26:20 -07:00
Owen
c4b3656fad Update UI to support additions on the resource 2026-05-06 10:09:05 -07:00
copilot-swe-agent[bot]
3ac315b52e Fix useEffect dependency array in create alert page
Agent-Logs-Url: https://github.com/fosrl/pangolin/sessions/4337e8e4-2110-45ae-bbf9-63f273d2a9a3

Co-authored-by: oschwartz10612 <4999704+oschwartz10612@users.noreply.github.com>
2026-05-06 16:55:38 +00:00
copilot-swe-agent[bot]
1b183d32c0 Hide alerting features when disable_enterprise_features is set
Agent-Logs-Url: https://github.com/fosrl/pangolin/sessions/4337e8e4-2110-45ae-bbf9-63f273d2a9a3

Co-authored-by: oschwartz10612 <4999704+oschwartz10612@users.noreply.github.com>
2026-05-06 16:54:58 +00:00
copilot-swe-agent[bot]
0c643e91a6 Initial plan 2026-05-06 16:52:05 +00:00
Owen
54c1dd3bae Make path the default 2026-05-05 21:05:42 -07:00
Owen
a8f4d2b7d1 Add new user and role selectors for pagination 2026-05-05 20:53:36 -07:00
Owen
fab53ba26a Dont show the link if not the org owner 2026-05-05 20:40:59 -07:00
Owen
62e19a2f4e Remove the hover effect 2026-05-05 20:10:14 -07:00
Owen
7d67fb9984 Make sure the domain is defined on a http resource 2026-05-05 20:07:06 -07:00
AstralDestiny
9f67134ce2 Update references of SSL alone to be TLS to align with proper wording and terms. 2026-05-05 21:30:36 -04:00
Owen
51f1693dbd Merge branch 'dev' into resource-policies 2026-05-05 18:02:27 -07:00
Owen Schwartz
7436aebca7 Merge pull request #2893 from Fredkiss3/feat/roles-and-user-multi-selectors
feat: roles & users selector
2026-05-05 17:36:40 -07:00
miloschwartz
66fda553e4 introduce caching in calculate func 2026-05-05 14:12:02 -07:00
Owen Schwartz
432dc81875 Merge pull request #3006 from fosrl/dev
don't await second calculate func
2026-05-05 13:46:05 -07:00
miloschwartz
2ecf076c0f don't await second calculate func 2026-05-05 12:37:52 -07:00
Fred KISSIE
0d04cc365f attach label to item 2026-05-05 21:35:10 +02:00
Owen Schwartz
9b71c426c7 Merge pull request #3005 from fosrl/dev
1.18.2-s.4
2026-05-05 12:12:09 -07:00
miloschwartz
e06dda27cb dont wait rebuild 2026-05-05 12:10:55 -07:00
Fred KISSIE
09baf2f32e 🗃️ add sqlite table for labels 2026-05-05 21:08:22 +02:00
Fred KISSIE
3253d60900 🚧 Add CRUD endpoints and tables for labels 2026-05-05 20:53:16 +02:00
miloschwartz
18f6e0f75d add subscribed check back 2026-05-05 11:52:31 -07:00
miloschwartz
3b232bcc58 set orgId to undefined 2026-05-05 11:31:58 -07:00
Owen
c575bb76e7 Fix only using acme.json in dir
Ref #2978
2026-05-05 11:11:43 -07:00
Owen Schwartz
87e6c7ba36 Merge pull request #3003 from fosrl/dev
1.18.2-s.3
2026-05-05 10:54:48 -07:00
Owen
b33a6e6fac Wipe the old tables if you are using inline 2026-05-04 20:54:43 -07:00
Owen
fc2c13a686 Add policies to blueprints 2026-05-04 20:44:04 -07:00
Owen
f4602a120e Merge branch 'dev' into resource-policies 2026-05-04 17:57:09 -07:00
Owen
c8e7e0ee1e WAL off default ENABLE_SQLITE_WAL_MODE to enable 2026-05-04 17:54:28 -07:00
Owen
7ccceeea0d Ignore extra sqlite files 2026-05-04 17:43:02 -07:00
Owen
f81f78f294 Merge branch 'dev' into resource-policies 2026-05-04 17:41:49 -07:00
Owen
6cab223f12 Adjust verify session queries to use policies 2026-05-04 17:30:10 -07:00
Owen Schwartz
0e7aafd364 Merge pull request #2998 from Josh-Voyles/mem-fix-2
fix: deterministically finalize SQLite prepared statements to prevent native memory leak (#2120)
2026-05-04 17:29:45 -07:00
Owen
7b05c02508 Adjust translation 2026-05-04 16:19:04 -07:00
Owen
5922bfb1a0 Fix API endpoint action issues 2026-05-04 16:01:40 -07:00
Owen
43f2e32231 Paywall resource policies 2026-05-04 15:30:49 -07:00
Owen
20ebdc6289 Fix openapi zod issue error 2026-05-04 15:04:54 -07:00
Owen
a80ae49a33 Support multiple roles 2026-05-04 14:54:20 -07:00
miloschwartz
91f1bae3e9 fix alignement in info sections 2026-05-04 14:51:17 -07:00
Owen
660197eef1 Merge branch 'feat/resource-policies' into resource-policies 2026-05-04 14:40:44 -07:00
miloschwartz
53c138ce3e use consistent button spacing 2026-05-04 14:34:32 -07:00
miloschwartz
969db14a3c remove delay in oidc validate 2026-05-04 13:14:35 -07:00
Fred KISSIE
1ca1059673 ♻️ 10 users/roles per page 2026-05-04 20:59:46 +02:00
Owen Schwartz
9410a18404 Merge pull request #2997 from fosrl/dev
Translations
2026-05-04 11:49:26 -07:00
Owen Schwartz
c1c387bdd8 Merge pull request #2996 from fosrl/crowdin_dev
New Crowdin updates
2026-05-04 11:48:58 -07:00
Owen Schwartz
6e83d77a87 New translations en-us.json (Spanish)
[ci skip]
2026-05-04 11:48:00 -07:00
Owen Schwartz
ba9a1efa4c New translations en-us.json (Norwegian Bokmal)
[ci skip]
2026-05-04 11:47:58 -07:00
Owen Schwartz
9e046b9608 New translations en-us.json (Chinese Simplified)
[ci skip]
2026-05-04 11:47:56 -07:00
Owen Schwartz
37794eb299 New translations en-us.json (Turkish)
[ci skip]
2026-05-04 11:47:55 -07:00
Owen Schwartz
4e66b0e74b New translations en-us.json (Russian)
[ci skip]
2026-05-04 11:47:53 -07:00
Owen Schwartz
44fa873977 New translations en-us.json (Portuguese)
[ci skip]
2026-05-04 11:47:51 -07:00
Owen Schwartz
505461a533 New translations en-us.json (Polish)
[ci skip]
2026-05-04 11:47:49 -07:00
Owen Schwartz
a88c5b1428 New translations en-us.json (Dutch)
[ci skip]
2026-05-04 11:47:47 -07:00
Owen Schwartz
97ef1d605c New translations en-us.json (Korean)
[ci skip]
2026-05-04 11:47:45 -07:00
Owen Schwartz
3fc1c9d948 New translations en-us.json (Italian)
[ci skip]
2026-05-04 11:47:44 -07:00
Owen Schwartz
68bd37ab6c New translations en-us.json (German)
[ci skip]
2026-05-04 11:47:42 -07:00
Owen Schwartz
5c317c535b New translations en-us.json (Czech)
[ci skip]
2026-05-04 11:47:40 -07:00
Owen Schwartz
37c6b11899 New translations en-us.json (Bulgarian)
[ci skip]
2026-05-04 11:47:38 -07:00
Owen Schwartz
45c567ffa0 New translations en-us.json (French)
[ci skip]
2026-05-04 11:47:36 -07:00
Fred KISSIE
49d22498fc ♻️ only select one role in CE and if user is non paying 2026-05-04 20:47:00 +02:00
Owen Schwartz
23f4302186 Merge pull request #2995 from fosrl/dev
1.18.2-s.2
2026-05-04 11:43:24 -07:00
Owen Schwartz
775ea64b55 Merge pull request #2977 from fosrl/newt-install-commands
fix(newt): update Helm install credentials and client flag handling
2026-05-04 11:40:19 -07:00
Owen
64ad7641af Add migration
Fixes #2968
Fixes #2990
2026-05-04 11:35:07 -07:00
Fred KISSIE
81274960f6 🚧 refactor 2026-05-04 20:22:16 +02:00
Owen
d724f5bb5d Add missing redirects and threshold to api
Fixes #2987
2026-05-04 10:46:11 -07:00
Fred KISSIE
30e627cca8 Merge branch 'dev' into feat/roles-and-user-multi-selectors 2026-05-04 18:49:19 +02:00
Fred KISSIE
53c1e2e742 ♻️ refactor 2026-05-04 18:45:31 +02:00
Owen Schwartz
fb4bda077b Merge pull request #2983 from fosrl/dev
1.18.2-s.1
2026-05-03 14:59:12 -07:00
Owen
d4f7c4a9c4 Merge branch 'dev' of github.com:fosrl/pangolin into dev 2026-05-03 14:46:59 -07:00
miloschwartz
1cc0e9b689 consolidate org idps in login form 2026-05-03 14:46:48 -07:00
Owen
584be4dbd2 Add badge 2026-05-03 14:45:42 -07:00
Owen
c33e295ce7 Add a banner showing that you are on a trial 2026-05-03 14:42:43 -07:00
Owen
1a926a7127 Handle trial limit lifecycle 2026-05-03 14:31:05 -07:00
miloschwartz
eb515a8f7f consolidate orgidps in import list 2026-05-03 14:16:36 -07:00
Owen
81b8a8a9e3 Fix ns cert generation 2026-05-03 12:29:48 -07:00
Owen
bcd164219f Try to speed up 2026-05-03 12:29:48 -07:00
Owen Schwartz
c90e405105 Merge pull request #2843 from Blacks-Army/dev
Exclude local/private/CGNAT IPs from geo-block rules (fixes  issue #2239)
2026-05-03 11:19:36 -07:00
rinseaid
4786fc3a31 Auto-create roles referenced in blueprints
When a blueprint references a role that doesn't exist, create it
automatically with default permissions (getOrg, getResource,
listResources) instead of throwing an error or silently dropping
the association.
2026-05-03 13:37:47 -04:00
Mustafa
b2c8311b26 Merge branch 'fosrl:dev' into dev 2026-05-03 18:53:48 +02:00
Josh Voyles
2154811ffb removed possible introduced HA Redis bug; improved comment 2026-05-03 09:39:27 -04:00
Marc Schäfer
1772ac220f fix(newt): update Helm install credentials and client flag handling
Use a Kubernetes Secret for Newt Helm chart credentials and configure the chart
with auth.existingSecretName instead of passing credential values through
auth.keys.*.

Add Helm-specific acceptClients handling so the generated Kubernetes command sets
newtInstances[0].acceptClients=true when client connections are enabled.
2026-05-03 15:07:42 +02:00
Josh Voyles
9bd33072f4 cleaned comments - more concise 2026-05-03 00:00:11 -04:00
Owen Schwartz
cf596d980f Merge pull request #2971 from fosrl/dev
1.18.2
2026-05-02 20:59:51 -07:00
Owen Schwartz
70f619b726 Merge pull request #2970 from fosrl/crowdin_dev
New Crowdin updates
2026-05-02 20:59:16 -07:00
Owen Schwartz
7743e3890b New translations en-us.json (Spanish)
[ci skip]
2026-05-02 20:57:57 -07:00
Owen Schwartz
d8df250555 New translations en-us.json (Norwegian Bokmal)
[ci skip]
2026-05-02 20:57:55 -07:00
Owen Schwartz
45c9f217c6 New translations en-us.json (Chinese Simplified)
[ci skip]
2026-05-02 20:57:54 -07:00
Owen Schwartz
8371692cc5 New translations en-us.json (Turkish)
[ci skip]
2026-05-02 20:57:52 -07:00
Owen Schwartz
5377dc7a1c New translations en-us.json (Russian)
[ci skip]
2026-05-02 20:57:51 -07:00
Owen Schwartz
02649468e0 New translations en-us.json (Portuguese)
[ci skip]
2026-05-02 20:57:49 -07:00
Owen Schwartz
c5ef00fb0e New translations en-us.json (Polish)
[ci skip]
2026-05-02 20:57:48 -07:00
Owen Schwartz
6f4325e9a0 New translations en-us.json (Dutch)
[ci skip]
2026-05-02 20:57:45 -07:00
Owen Schwartz
a2a031dfe7 New translations en-us.json (Korean)
[ci skip]
2026-05-02 20:57:44 -07:00
Owen Schwartz
e34a4c82eb New translations en-us.json (Italian)
[ci skip]
2026-05-02 20:57:42 -07:00
Owen Schwartz
52fd7df727 New translations en-us.json (German)
[ci skip]
2026-05-02 20:57:41 -07:00
Owen Schwartz
d5f08437d7 New translations en-us.json (Czech)
[ci skip]
2026-05-02 20:57:39 -07:00
Owen Schwartz
9ee07ba343 New translations en-us.json (Bulgarian)
[ci skip]
2026-05-02 20:57:38 -07:00
Owen Schwartz
4baaa5fc14 New translations en-us.json (French)
[ci skip]
2026-05-02 20:57:36 -07:00
Owen
61de100630 Fix imports 2026-05-02 20:46:52 -07:00
miloschwartz
3694f43ae8 dont early return on multi org 2026-05-02 20:38:14 -07:00
Owen
279211142d Bump version 2026-05-02 13:48:25 -07:00
Owen
b8822b4d25 Fix CE not processing alert status
Fixes #2968
2026-05-02 13:38:05 -07:00
Josh Voyles
0655ba9423 fix: revert investigative changes, keep root cause fixes only
Reverts diagnostic instrumentation and defensive hardening added during
memory leak investigation. Only root cause fixes survive.

Root causes fixed:
- SQLite driver: auto-finalize wrapper + PRAGMAs
- WS routers: delete clientConfigVersions on disconnect (unbounded Map leak)
- WS private router: same + Redis key cleanup

Reverted:
- Memory monitor, rate limiting, request timeouts (diagnostic/hardening)
- shutdownAuditLogger wiring, audit re-queue change, debug logs (cleanup/secondary)
- package-lock.json drift
2026-05-02 16:33:13 -04:00
Owen
e1afbc226c Allow configuring the webhook body 2026-05-02 13:26:54 -07:00
miloschwartz
96c450fd08 update private resources screenshot 2026-05-02 13:19:00 -07:00
Owen Schwartz
587e4d104b New translations en-us.json (Spanish)
[ci skip]
2026-05-02 13:16:28 -07:00
Owen Schwartz
368c5c374f New translations en-us.json (Norwegian Bokmal)
[ci skip]
2026-05-02 13:16:26 -07:00
Owen Schwartz
7675b6409c New translations en-us.json (Chinese Simplified)
[ci skip]
2026-05-02 13:16:24 -07:00
Owen Schwartz
d31da1a41e New translations en-us.json (Turkish)
[ci skip]
2026-05-02 13:16:23 -07:00
Owen Schwartz
49e259e259 New translations en-us.json (Russian)
[ci skip]
2026-05-02 13:16:21 -07:00
Owen Schwartz
f4684c1858 New translations en-us.json (Portuguese)
[ci skip]
2026-05-02 13:16:19 -07:00
Owen Schwartz
6e223bb363 New translations en-us.json (Polish)
[ci skip]
2026-05-02 13:16:18 -07:00
Owen Schwartz
22e7038b2c New translations en-us.json (Dutch)
[ci skip]
2026-05-02 13:16:16 -07:00
Owen Schwartz
76ba4c1fdf New translations en-us.json (Korean)
[ci skip]
2026-05-02 13:16:14 -07:00
Owen Schwartz
7f25d94a83 New translations en-us.json (Italian)
[ci skip]
2026-05-02 13:16:13 -07:00
Owen Schwartz
769ba27e3a New translations en-us.json (German)
[ci skip]
2026-05-02 13:16:11 -07:00
Owen Schwartz
a188552ba0 New translations en-us.json (Czech)
[ci skip]
2026-05-02 13:16:09 -07:00
Owen Schwartz
208132082e New translations en-us.json (Bulgarian)
[ci skip]
2026-05-02 13:16:08 -07:00
Owen Schwartz
fcd5789221 New translations en-us.json (French)
[ci skip]
2026-05-02 13:16:06 -07:00
Josh Voyles
2c85bcd06b fix(db): deterministically finalize prepared statements after execution
Wrap Statement .all()/.get()/.run() via autoFinalizeStatement() with
try/finally calling stmt.finalize() post-execution, releasing native
sqlite3_stmt memory immediately instead of waiting for GC.

Safe because:
- Drizzle one-time queries invoke each statement once only
- Drizzle does not access statement after .all()/.get()/.run() returns
- Migration scripts use isolated new Database() instances (unpatched)
- No app code holds persistent .prepare() refs on main db
2026-05-02 15:50:54 -04:00
miloschwartz
c6a8b09cff log in page improvements 2026-05-02 12:46:39 -07:00
miloschwartz
380ff381fc fix credenza scroll extra spacing above footer 2026-05-02 12:19:00 -07:00
Owen Schwartz
5eb3951f00 New translations en-us.json (Spanish)
[ci skip]
2026-05-02 12:13:08 -07:00
Owen Schwartz
c30e94da98 New translations en-us.json (Norwegian Bokmal)
[ci skip]
2026-05-02 12:13:06 -07:00
Owen Schwartz
6ca24d51a1 New translations en-us.json (Chinese Simplified)
[ci skip]
2026-05-02 12:13:05 -07:00
Owen Schwartz
13f512aed6 New translations en-us.json (Turkish)
[ci skip]
2026-05-02 12:13:03 -07:00
Owen Schwartz
2bdbc9d688 New translations en-us.json (Russian)
[ci skip]
2026-05-02 12:13:02 -07:00
Owen Schwartz
8e2f30d8de New translations en-us.json (Portuguese)
[ci skip]
2026-05-02 12:13:00 -07:00
Owen Schwartz
a84e1cc9e0 New translations en-us.json (Polish)
[ci skip]
2026-05-02 12:12:58 -07:00
Owen Schwartz
6b28f0c81e New translations en-us.json (Dutch)
[ci skip]
2026-05-02 12:12:56 -07:00
Owen Schwartz
d28d3ba6ea New translations en-us.json (Korean)
[ci skip]
2026-05-02 12:12:55 -07:00
Owen Schwartz
6efaf9f40d New translations en-us.json (Italian)
[ci skip]
2026-05-02 12:12:53 -07:00
Owen Schwartz
5379b32959 New translations en-us.json (German)
[ci skip]
2026-05-02 12:12:51 -07:00
Owen Schwartz
9bb936a40d New translations en-us.json (Czech)
[ci skip]
2026-05-02 12:12:50 -07:00
Owen Schwartz
960fe760f1 New translations en-us.json (Bulgarian)
[ci skip]
2026-05-02 12:12:48 -07:00
Owen Schwartz
2f2105a085 New translations en-us.json (French)
[ci skip]
2026-05-02 12:12:46 -07:00
miloschwartz
de92a28435 update mac models 2026-05-02 12:09:55 -07:00
Owen
d8c3484ed5 Have to import from private 2026-05-02 12:00:51 -07:00
Owen
726e000154 Show remote nodes update in table 2026-05-02 11:55:01 -07:00
Josh Voyles
d6abe83fdc fix: memory improvements
- SQLite: enable WAL mode and PRAGMA performance settings

- ws.ts (public + private): fix clientConfigVersions memory leak

- internal server: add rate limiting and request timeouts

- audit log: fix flush re-queue feedback loop

- memory: add monitoring instrumentation

- security: remove debug log of full request body
2026-05-02 07:37:18 -04:00
Owen Schwartz
9df46f7014 Merge pull request #2966 from fosrl/dev
Try to pull domains from host regex
2026-05-01 20:54:09 -07:00
Owen
908f0d54e2 Try to pull domains from host regex 2026-05-01 20:53:39 -07:00
Milo Schwartz
f0010ea12a Merge pull request #2965 from fosrl/dev
add new screenshots
2026-05-01 17:35:29 -07:00
miloschwartz
cab8be1a9a add new screenshots 2026-05-01 17:34:05 -07:00
Owen Schwartz
0a9dab7cca Merge pull request #2964 from fosrl/dev
Update translations
2026-05-01 17:02:41 -07:00
Owen Schwartz
889ab1f8a8 Merge pull request #2963 from fosrl/crowdin_dev
New Crowdin updates
2026-05-01 17:02:10 -07:00
Owen Schwartz
a9019cfb23 New translations en-us.json (Spanish)
[ci skip]
2026-05-01 17:00:49 -07:00
Owen Schwartz
441d4bce6e New translations en-us.json (Norwegian Bokmal)
[ci skip]
2026-05-01 17:00:47 -07:00
Owen Schwartz
dd1e681a9c New translations en-us.json (Chinese Simplified)
[ci skip]
2026-05-01 17:00:45 -07:00
Owen Schwartz
a882619eaf New translations en-us.json (Turkish)
[ci skip]
2026-05-01 17:00:43 -07:00
Owen Schwartz
f43baaaf1f New translations en-us.json (Russian)
[ci skip]
2026-05-01 17:00:41 -07:00
Owen Schwartz
c3dc0bd015 New translations en-us.json (Portuguese)
[ci skip]
2026-05-01 17:00:39 -07:00
Owen Schwartz
1fd2a0fae2 New translations en-us.json (Polish)
[ci skip]
2026-05-01 17:00:37 -07:00
Owen Schwartz
8ba5b43569 New translations en-us.json (Dutch)
[ci skip]
2026-05-01 17:00:35 -07:00
Owen Schwartz
6deefcd003 New translations en-us.json (Korean)
[ci skip]
2026-05-01 17:00:33 -07:00
Owen Schwartz
4d6cea5fcd New translations en-us.json (Italian)
[ci skip]
2026-05-01 17:00:31 -07:00
Owen Schwartz
f175ac774f New translations en-us.json (German)
[ci skip]
2026-05-01 17:00:29 -07:00
Owen Schwartz
0fe2b24f6b New translations en-us.json (Czech)
[ci skip]
2026-05-01 17:00:27 -07:00
Owen Schwartz
6ad06e6faf New translations en-us.json (Bulgarian)
[ci skip]
2026-05-01 17:00:25 -07:00
Owen Schwartz
d47faeced1 New translations en-us.json (French)
[ci skip]
2026-05-01 17:00:23 -07:00
Owen Schwartz
498f586eeb New translations en-us.json (Spanish)
[ci skip]
2026-05-01 16:57:38 -07:00
Owen Schwartz
e94fc6bc65 New translations en-us.json (Norwegian Bokmal)
[ci skip]
2026-05-01 16:57:37 -07:00
Owen Schwartz
0a1fe1b725 New translations en-us.json (Chinese Simplified)
[ci skip]
2026-05-01 16:57:35 -07:00
Owen Schwartz
eb40b04b43 New translations en-us.json (Turkish)
[ci skip]
2026-05-01 16:57:33 -07:00
Owen Schwartz
6685afdcf9 New translations en-us.json (Russian)
[ci skip]
2026-05-01 16:57:32 -07:00
Owen Schwartz
49232e32bf New translations en-us.json (Portuguese)
[ci skip]
2026-05-01 16:57:30 -07:00
Owen Schwartz
aec0aed211 New translations en-us.json (Polish)
[ci skip]
2026-05-01 16:57:28 -07:00
Owen Schwartz
d43b3176f5 New translations en-us.json (Dutch)
[ci skip]
2026-05-01 16:57:26 -07:00
Owen Schwartz
190074ea0c New translations en-us.json (Korean)
[ci skip]
2026-05-01 16:57:24 -07:00
Owen Schwartz
c5a7719239 New translations en-us.json (Italian)
[ci skip]
2026-05-01 16:57:22 -07:00
Owen Schwartz
5eac131d2e New translations en-us.json (German)
[ci skip]
2026-05-01 16:57:21 -07:00
Owen Schwartz
0bc3276ee2 New translations en-us.json (Czech)
[ci skip]
2026-05-01 16:57:18 -07:00
Owen Schwartz
5073507b90 New translations en-us.json (Bulgarian)
[ci skip]
2026-05-01 16:57:16 -07:00
Owen Schwartz
805e6f856a New translations en-us.json (French)
[ci skip]
2026-05-01 16:57:14 -07:00
Owen Schwartz
412a9b5294 Merge pull request #2962 from fosrl/dev
1.18.1-s.6
2026-05-01 16:54:37 -07:00
Owen
fbf95c5363 Start creating ns one level down 2026-05-01 16:51:42 -07:00
Owen
b907850344 Add missing heading 2026-05-01 16:51:42 -07:00
miloschwartz
22116373e3 increase target site selector width 2026-05-01 16:33:40 -07:00
miloschwartz
9757c3d8b6 show newt version on site 2026-05-01 16:26:45 -07:00
miloschwartz
f8b85d4b4e fix sidebar product updates spacing 2026-05-01 16:14:06 -07:00
Owen
4651f19c53 Support acme_json_path as a directory of acme file
Fixes #2961
2026-05-01 16:06:37 -07:00
Owen
4524bdc094 Add http cert syncing for use with the controller 2026-05-01 15:42:38 -07:00
Owen Schwartz
741850880e Merge pull request #2959 from fosrl/dev
1.18.1-s.4
2026-05-01 15:05:59 -07:00
Owen
53e096f7cb Allow deleting account with trial 2026-05-01 15:01:48 -07:00
Owen
3dfd7e8a43 Update limits 2026-05-01 11:47:14 -07:00
Owen
db6e60d0a3 Adjust language 2026-05-01 10:48:09 -07:00
Owen
54d2d689c1 Run messaging for delete in the background as well 2026-04-30 14:38:03 -07:00
Owen Schwartz
bb5853827b Merge pull request #2948 from fosrl/dev
1.18.1-s.3
2026-04-30 14:11:16 -07:00
Owen
68f5512732 Handle messaging in the background; dont time out 2026-04-30 14:00:32 -07:00
Fred KISSIE
657072dd17 💄 fix input styles for tags 2026-04-30 22:06:36 +02:00
Fred KISSIE
443a19165f ♻️ refactor 2026-04-30 22:02:23 +02:00
Fred KISSIE
b4906ec9ba ♻️ replace roles tag with roles selector in role config fields 2026-04-30 22:01:46 +02:00
Owen
416e124c02 Rotate the secret on the new things using it 2026-04-30 11:53:55 -07:00
Owen
d3e4d8cda8 Fix pr blueprints not picking up site 2026-04-30 11:39:37 -07:00
Owen
81972dbb73 Add name to migration
Fixes #2943
2026-04-30 10:56:12 -07:00
Fred KISSIE
39bf64bc35 Merge branch 'dev' into feat/roles-and-user-multi-selectors 2026-04-30 16:55:25 +02:00
Owen Schwartz
b715786a1e Merge pull request #2939 from fosrl/dev
1.18.1-s.2
2026-04-29 21:33:03 -07:00
Owen
ae24eb2d2c Disable the alerts and hc when downgrading 2026-04-29 21:31:02 -07:00
Owen
20fc59dcda Delete trial when upgrading 2026-04-29 21:25:58 -07:00
Owen
93b09de425 Adjust cloud api endpoints 2026-04-29 21:04:11 -07:00
Owen
bacc130453 Clean up sign and verify 2026-04-29 17:14:22 -07:00
Owen Schwartz
79541ec7b8 Merge pull request #2936 from fosrl/dev
1.18.1 patch over
2026-04-29 16:43:06 -07:00
Owen
81197f8a86 Update the database if the wildcard changes 2026-04-29 16:42:10 -07:00
miloschwartz
dcfc7822f4 hide cert in public resources col on oss 2026-04-29 16:03:59 -07:00
Owen Schwartz
269bd9aa0f Merge pull request #2934 from fosrl/dev
1.18.1-s.1
2026-04-29 15:18:28 -07:00
Owen
0a0817b860 Restrict alerting 2026-04-29 15:15:53 -07:00
Owen Schwartz
b7a903ab32 Merge pull request #2933 from fosrl/dev
1.18.1
2026-04-29 15:00:29 -07:00
Owen Schwartz
ab60438aa7 Merge pull request #2917 from fosrl/crowdin_dev
New Crowdin updates
2026-04-29 14:55:53 -07:00
Owen Schwartz
b9f3f90de6 New translations en-us.json (Spanish)
[ci skip]
2026-04-29 14:54:32 -07:00
Owen Schwartz
b53cc397be New translations en-us.json (Norwegian Bokmal)
[ci skip]
2026-04-29 14:54:30 -07:00
Owen Schwartz
994fb456c2 New translations en-us.json (Chinese Simplified)
[ci skip]
2026-04-29 14:54:29 -07:00
Owen Schwartz
b36927c7a0 New translations en-us.json (Turkish)
[ci skip]
2026-04-29 14:54:27 -07:00
Owen Schwartz
1c57473b6d New translations en-us.json (Russian)
[ci skip]
2026-04-29 14:54:25 -07:00
Owen Schwartz
c02c3eaa4a New translations en-us.json (Portuguese)
[ci skip]
2026-04-29 14:54:23 -07:00
Owen Schwartz
3c265ee577 New translations en-us.json (Polish)
[ci skip]
2026-04-29 14:54:22 -07:00
Owen Schwartz
98dfd05f06 New translations en-us.json (Dutch)
[ci skip]
2026-04-29 14:54:20 -07:00
Owen Schwartz
faa2e97530 New translations en-us.json (Korean)
[ci skip]
2026-04-29 14:54:18 -07:00
Owen Schwartz
175f10a51d New translations en-us.json (Italian)
[ci skip]
2026-04-29 14:54:16 -07:00
Owen Schwartz
6284930fce New translations en-us.json (German)
[ci skip]
2026-04-29 14:54:15 -07:00
Owen Schwartz
6c93aca444 New translations en-us.json (Czech)
[ci skip]
2026-04-29 14:54:13 -07:00
Owen Schwartz
d83318cbfc New translations en-us.json (Bulgarian)
[ci skip]
2026-04-29 14:54:11 -07:00
Owen Schwartz
143f362a48 New translations en-us.json (French)
[ci skip]
2026-04-29 14:54:09 -07:00
miloschwartz
698cd868a8 show cert status in public reosurces table 2026-04-29 14:47:34 -07:00
Owen
a55842ffff Scrape certs from ALL resolvers 2026-04-29 14:29:15 -07:00
Owen
2ffe254879 Dont include site resources on the cloud 2026-04-29 14:08:42 -07:00
miloschwartz
e173f59d89 visual improvements 2026-04-29 13:44:35 -07:00
miloschwartz
d3870f4920 cert status in priv resources table first pass 2026-04-29 13:05:26 -07:00
miloschwartz
227501d8f8 fix rounded buttons in target input 2026-04-29 12:39:08 -07:00
miloschwartz
a16f805709 fix style for unknown status 2026-04-29 12:36:47 -07:00
miloschwartz
a029b107ae dont show site online status for local sites 2026-04-29 12:35:08 -07:00
miloschwartz
f03389a9a0 fix cert styling 2026-04-29 12:18:52 -07:00
Owen
78fff6bfde Filter to only allow newt sites 2026-04-29 12:18:28 -07:00
Owen
bc585c24fc Calculate actual resource status
Fixes #2930
2026-04-29 12:07:32 -07:00
miloschwartz
0f6c66dc67 use localfont and updated mona sans closes #2924 2026-04-29 11:58:06 -07:00
Owen
6be150bafe Handle possible not null for tcp, udp, and icmp
Fixes #2929
2026-04-29 11:42:18 -07:00
Owen
1eac7741a5 Show the certs elsewhere when required 2026-04-29 11:34:10 -07:00
Owen
b8ca0499af Dont show the cert box oss and dont check license 2026-04-29 11:28:30 -07:00
Owen
b39a2bcfb1 Quiet logs 2026-04-29 11:25:43 -07:00
Owen
d45b727dca Dont show cert status because not saved yet 2026-04-29 11:06:14 -07:00
Owen
5c31d35e28 Handle sans in the acme.json 2026-04-29 10:59:49 -07:00
Owen
8c645315f3 Handle when siteIds is not provided 2026-04-29 10:59:36 -07:00
Milo Schwartz
ab6377e086 Merge pull request #2923 from fosrl/miloschwartz-patch-2
Update README.md
2026-04-28 23:03:31 -07:00
Milo Schwartz
8685cf4208 Update README.md 2026-04-29 02:03:18 -04:00
Fred KISSIE
a3f30eff02 ♻️ remove unused code and imports 2026-04-29 07:29:20 +02:00
Fred KISSIE
081940dff8 replace roles & users in uptime alert section 2026-04-29 07:29:05 +02:00
Owen Schwartz
26fe1259da Merge pull request #2922 from fosrl/dev
1.18.0-s.2
2026-04-28 22:28:35 -07:00
Owen
3bcbeb24f3 Query the right column 2026-04-28 22:27:35 -07:00
Owen
1d0a92c83e Its in the transaction so we wait 2026-04-28 22:22:06 -07:00
Owen
a44100c2bd Handle deleting client and orphaning resources 2026-04-28 22:19:22 -07:00
Fred KISSIE
c4cf4cdec4 ♻️ show idp name in user selector 2026-04-29 06:57:49 +02:00
miloschwartz
2203ebf723 show user idp in devices 2026-04-28 21:27:11 -07:00
Owen Schwartz
70958185bd Merge pull request #2921 from fosrl/dev
1.18.0-s.1
2026-04-28 21:03:36 -07:00
Owen
7e374baee9 Update if the ssl toggle changes 2026-04-28 20:45:20 -07:00
Owen
4cf6ca1d55 Force tcp and udp ports when http mode 2026-04-28 20:27:27 -07:00
Fred KISSIE
85f2165a1e ♻️ refactor multi select components 2026-04-29 05:19:36 +02:00
Fred KISSIE
1bc7175dd4 replace user select in resource auth and alert rule field 2026-04-29 05:19:23 +02:00
Owen Schwartz
2957d6592d Merge pull request #2919 from fosrl/dev
Add missing imports
2026-04-28 16:52:53 -07:00
Owen
de2a22aad8 Add missing imports 2026-04-28 16:52:35 -07:00
Owen Schwartz
b96db4f133 Merge pull request #2918 from fosrl/dev
Use logsDb for the status history
2026-04-28 16:38:43 -07:00
Owen
2a29062659 Use logsDb for the status history 2026-04-28 16:38:13 -07:00
Owen Schwartz
8ed9adbfae New translations en-us.json (German)
[ci skip]
2026-04-28 16:21:16 -07:00
Owen Schwartz
98406f63af Merge pull request #2916 from fosrl/dev
1.18.0
2026-04-28 15:53:31 -07:00
Owen
85415176ab Clean imports 2026-04-28 15:41:00 -07:00
Owen
b81ae3d998 Seed satatus data for resources, sites, and hc 2026-04-28 15:15:09 -07:00
Owen
208289f498 Select all networks to prevent delete issues 2026-04-28 12:02:21 -07:00
Owen
8783c47a3c Dont allow clicking the wildcard resource link 2026-04-28 11:21:32 -07:00
Owen
592ca64253 Fix delete 2026-04-28 09:57:46 -07:00
Owen Schwartz
1de6e58eef Merge pull request #2912 from fosrl/crowdin_dev
New Crowdin updates
2026-04-27 21:23:34 -07:00
Owen Schwartz
92822a20e8 New translations en-us.json (Spanish) 2026-04-27 20:25:22 -07:00
Owen Schwartz
4a3035d597 New translations en-us.json (Norwegian Bokmal) 2026-04-27 20:25:20 -07:00
Owen Schwartz
bd866a5fd2 New translations en-us.json (Chinese Simplified) 2026-04-27 20:25:18 -07:00
Owen Schwartz
1c6cd57c31 New translations en-us.json (Turkish) 2026-04-27 20:25:16 -07:00
Owen Schwartz
a0619868be New translations en-us.json (Russian) 2026-04-27 20:25:15 -07:00
Owen Schwartz
6c2dd4331a New translations en-us.json (Portuguese) 2026-04-27 20:25:13 -07:00
Owen Schwartz
5e6171263b New translations en-us.json (Polish) 2026-04-27 20:25:11 -07:00
Owen Schwartz
d33c704f76 New translations en-us.json (Dutch) 2026-04-27 20:25:09 -07:00
Owen Schwartz
3cb1cd9f2f New translations en-us.json (Korean) 2026-04-27 20:25:07 -07:00
Owen Schwartz
926fe5e474 New translations en-us.json (Italian) 2026-04-27 20:25:06 -07:00
Owen Schwartz
243da6379b New translations en-us.json (German) 2026-04-27 20:25:04 -07:00
Owen Schwartz
68ea7d1d98 New translations en-us.json (Czech) 2026-04-27 20:25:02 -07:00
Owen Schwartz
c0a4541455 New translations en-us.json (Bulgarian) 2026-04-27 20:25:00 -07:00
Owen Schwartz
e4bf2da2e5 New translations en-us.json (French) 2026-04-27 20:24:58 -07:00
Fred KISSIE
ddaa9c32a7 ♻️ replace roles & user selectors in machines & create user 2026-04-28 05:08:20 +02:00
Owen
85334f082c Only support alerts and newt sync on saas 2026-04-27 18:20:30 -07:00
Owen
c771722127 Dont include rewrite to 2026-04-27 17:52:41 -07:00
Owen
f89b0a17ac Set the default to unknown 2026-04-27 17:15:44 -07:00
Owen
81a6fb8d00 Dont import from postgres 2026-04-27 17:04:04 -07:00
Owen
dbee049ac8 Fix oss build issues 2026-04-27 16:30:54 -07:00
Owen
c03519b7f5 Send updates when the full domain changes 2026-04-27 16:19:37 -07:00
Owen
7affaf63d0 Update get cert to now allow restarting 2026-04-27 16:19:37 -07:00
Owen
08e9cb862d Fix deleting resource 2026-04-27 16:19:36 -07:00
miloschwartz
cbb2388a46 add multi site help link 2026-04-27 15:38:19 -07:00
Owen
24f437e260 Cap degraded in the mail 2026-04-27 15:24:16 -07:00
Owen
3439a3690f Fix site offline not respecting hc enabled 2026-04-27 15:24:16 -07:00
Owen
b88469f901 Hide the icmp and snmp for now 2026-04-27 15:24:16 -07:00
miloschwartz
e573125934 update wildcard resources link 2026-04-27 15:14:29 -07:00
Owen
c5072bed80 Fix healthcheck not showing data 2026-04-27 14:33:28 -07:00
Owen
28dd06c41f Add caching to the hc and fix resource stuff 2026-04-27 14:29:57 -07:00
Owen
61aaa5a832 Wrap in transactions 2026-04-27 13:46:15 -07:00
Owen
512ba2150b Fix sanitizing the domain causing problems 2026-04-27 13:46:15 -07:00
Owen
d1f7a9c6df Add health to the resource 2026-04-27 13:46:15 -07:00
miloschwartz
1cdb261f7e add loading indicator to resources 2026-04-27 12:31:31 -07:00
Owen
17631599a2 Remove delay 2026-04-26 21:25:53 -07:00
Owen
7563b37cd0 Add missing health column 2026-04-26 21:25:14 -07:00
Owen
7318c86cca Fix display and query issues 2026-04-26 20:33:58 -07:00
Owen
467cd70b72 Handle delete correctly 2026-04-26 20:26:03 -07:00
dependabot[bot]
f286d66cbc Bump actions/setup-node from 6.3.0 to 6.4.0
Bumps [actions/setup-node](https://github.com/actions/setup-node) from 6.3.0 to 6.4.0.
- [Release notes](https://github.com/actions/setup-node/releases)
- [Commits](53b83947a5...48b55a011b)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-27 01:36:02 +00:00
Owen
8ca72a39da Handle deleting targets 2026-04-26 17:55:26 -07:00
Owen
4ff811c5bd Use http by default 2026-04-26 17:38:24 -07:00
Owen
ca2370e31d Add logging when manually changing the hc status 2026-04-26 17:29:20 -07:00
miloschwartz
06af53c4d6 increase refresh rate 2026-04-26 16:57:10 -07:00
miloschwartz
6befdfe01e improve cert restart button style 2026-04-26 16:50:52 -07:00
Owen
5695137280 Dont create alerts with 300 second cooldowns 2026-04-26 16:43:28 -07:00
miloschwartz
e2e0936f43 improve cert status style 2026-04-26 11:27:53 -07:00
miloschwartz
32d8bde96d adjust wildcard placeholder 2026-04-26 11:15:23 -07:00
miloschwartz
f24f867684 add hyphens to random blueprint name 2026-04-26 11:11:31 -07:00
miloschwartz
491636851f Merge branch 'dev' of https://github.com/fosrl/pangolin into dev 2026-04-26 10:23:36 -07:00
Owen
bf1870608b Exclude wildcard resources 2026-04-25 15:51:39 -07:00
miloschwartz
6f6c24b6df use semibold 2026-04-25 15:42:19 -07:00
Owen
7c7d1f641e Support unknown and degraded status 2026-04-25 15:34:04 -07:00
Owen
82212af643 Add resource degraded 2026-04-25 15:34:04 -07:00
miloschwartz
8e16ff07a9 move switch toggle above tabs on health check dialog 2026-04-25 15:23:22 -07:00
miloschwartz
56816c7584 change column order on sites table 2026-04-25 15:17:39 -07:00
miloschwartz
477712b73c show site resources 2026-04-25 15:08:08 -07:00
Fred KISSIE
27b2ec309d 🚧 users selector 2026-04-25 06:18:13 +02:00
Fred KISSIE
91ce8bea4b 🔨 add local mailer for catching emails 2026-04-25 05:59:43 +02:00
Fred KISSIE
2ea9d27237 machine selector 2026-04-25 05:26:41 +02:00
Fred KISSIE
95cbaaae21 new multi select tag input 2026-04-25 04:47:31 +02:00
Fred KISSIE
955aa41f53 revert changes modifying existing tag input 2026-04-25 04:47:17 +02:00
Fred KISSIE
cb3fa028c3 ♻️ create custom autocomplete tag input 2026-04-25 04:10:54 +02:00
Owen
ecacb26445 Merge branch 'dev' of github.com:fosrl/pangolin into dev 2026-04-24 17:32:28 -07:00
Owen
cca7cea2f1 Handeling the different health status 2026-04-24 17:30:54 -07:00
miloschwartz
07154d2a16 add links to view resources on site 2026-04-24 17:07:11 -07:00
miloschwartz
b509c8aeec dont distribute info section cols 2026-04-24 16:57:53 -07:00
miloschwartz
a2c76cbb24 set standard filter popover width 2026-04-24 16:44:57 -07:00
miloschwartz
960ada4d66 add site column and filter to public resources 2026-04-24 16:24:26 -07:00
Owen
34296e5f40 Fix health check status 2026-04-24 16:19:35 -07:00
miloschwartz
33f1662c91 support site filter in private resources table 2026-04-24 16:12:15 -07:00
Owen
29f26021df Add the right pending record 2026-04-24 16:07:44 -07:00
Owen
15f02cf79a Quiet up messages 2026-04-24 16:06:19 -07:00
Owen
2a5d836747 Fix gear icon 2026-04-24 16:06:04 -07:00
Owen
593a7fdd69 Merge branch 'dev' of github.com:fosrl/pangolin into dev 2026-04-24 15:13:25 -07:00
miloschwartz
99f9b68efe fix full sudo mode calculation 2026-04-24 14:53:11 -07:00
miloschwartz
9655f119a5 fix text 2026-04-24 13:47:54 -07:00
Owen
48ddc700a0 Catch the domains the right way 2026-04-24 13:40:31 -07:00
Owen
0473d5f639 Get the cert correctly 2026-04-24 12:18:50 -07:00
Owen
537f9ae66b Always update the domain even if wildcard changes 2026-04-24 12:14:06 -07:00
Owen
d08f276794 Use the provided host in the cookie 2026-04-24 11:55:09 -07:00
Fred KISSIE
c746e1bc8d 🚧 wip 2026-04-24 08:33:43 +02:00
Owen
6a96f743aa Update exchange session to support wildcards 2026-04-23 21:38:12 -07:00
Owen
b4f0b4e285 Handle matching wildcards 2026-04-23 21:25:13 -07:00
Owen
07c7501669 New columns 2026-04-23 20:30:34 -07:00
Owen
009bac64bf Adding guiderails 2026-04-23 18:02:32 -07:00
Owen
5e293e8364 Handle getting resources 2026-04-23 17:14:05 -07:00
Fred KISSIE
da4dd88fdd Merge branch 'dev' into feat/roles-and-user-multi-selectors 2026-04-24 00:40:17 +02:00
Owen
1ba7fca798 Update traefik config 2026-04-23 15:08:55 -07:00
Owen
e7a9a19816 Basic crud working? 2026-04-23 15:01:43 -07:00
Owen
fa117198a0 Pass one at getting it into the db 2026-04-23 14:05:08 -07:00
Owen
f03d0cd47f Merge branch 'dev' of github.com:fosrl/pangolin into dev 2026-04-23 13:37:44 -07:00
Owen Schwartz
925a59c080 Merge pull request #2873 from sidd190/fix/crowdsec-traefik-logrotate
(fix): Added a logrotate function to the crowdsec.go installer file
2026-04-23 12:18:00 -07:00
Owen
a7c7319407 Deprecated sites should be optional 2026-04-23 12:09:22 -07:00
Fred KISSIE
b9bee2836b 🚧 wip 2026-04-23 06:33:57 +02:00
Owen
230f77118a Also check when getting the cert 2026-04-22 21:11:52 -07:00
Owen
bcb5b7b4a7 Show status in messages 2026-04-22 20:44:35 -07:00
Owen
90a2ed2f10 Create pending cert 2026-04-22 20:39:04 -07:00
Owen
fc69364feb Show cert status 2026-04-22 20:36:00 -07:00
Fred KISSIE
53c48e6f04 🌐 update french translations 2026-04-23 05:17:33 +02:00
Fred KISSIE
9db5ff9ff7 ♻️ small refactor 2026-04-23 04:22:18 +02:00
Owen
245755a140 Use transactions 2026-04-22 18:13:15 -07:00
Owen
dcbd22b4ad Handle all of the alerting from the functions 2026-04-22 18:13:15 -07:00
miloschwartz
8481b0a073 dont filter admin role in role selector for alerts 2026-04-22 17:52:31 -07:00
miloschwartz
f651ca84fa remove empty table state lines 2026-04-22 17:43:29 -07:00
miloschwartz
6b83d3c3f1 add meta titles to alert pages 2026-04-22 17:27:30 -07:00
Owen
d463a578c2 Handle *. wildcard domains in the db 2026-04-22 17:06:22 -07:00
Owen
9d0a8ecb09 Update placeholder and handle wildcard certs 2026-04-22 16:48:51 -07:00
Owen
af5394d464 Add more information about caches 2026-04-22 16:48:51 -07:00
miloschwartz
c956e0d401 add meta titles to auth pages 2026-04-22 16:09:16 -07:00
miloschwartz
2a281ec002 update telemetry 2026-04-22 15:06:37 -07:00
miloschwartz
4c000c1d49 add site online indicator to selector 2026-04-22 14:33:28 -07:00
miloschwartz
ea4ff75552 cosmetic adjustments 2026-04-22 14:25:06 -07:00
Owen
c78b866087 Add translations 2026-04-22 14:04:21 -07:00
miloschwartz
48b6e98bbc visual improvements 2026-04-22 12:25:01 -07:00
Owen
3d5260b13e Fix strings and local sites 2026-04-22 12:23:59 -07:00
miloschwartz
d0b0d95b9a fix squished alert card when disabled 2026-04-22 12:16:39 -07:00
miloschwartz
c2c8b7a631 disable overflow on header row for tables 2026-04-22 12:08:57 -07:00
Owen
9bc11b8717 Merge branch 'main' into dev 2026-04-22 11:38:14 -07:00
miloschwartz
1d53211fe0 fix logo size 2026-04-21 23:16:06 -07:00
Siddharth Bansal
473bce856d Pass installdir as a parameter 2026-04-20 21:36:42 +05:30
Siddharth Bansal
2c8b7b5ca5 (fix): Added a logrotate function to the crowdsec.go installer file 2026-04-19 12:33:59 +05:30
Mustafa
8e1905a695 Exclude local/private/CGNAT IPs from COUNTRY=ALL and ASN=ALL/AS0 geo-blocking rules 2026-04-12 20:19:32 +02:00
Fred KISSIE
f3eb823bc3 🐛 fix sqlite tables 2026-03-12 22:36:29 +01:00
Fred KISSIE
61c13db090 Merge branch 'dev' into feat/resource-policies 2026-03-12 22:19:37 +01:00
Fred KISSIE
ccbd793f52 💬 show error 2026-03-12 22:13:27 +01:00
Fred KISSIE
d13e6896a8 ♻️ update 2026-03-12 22:11:39 +01:00
Fred KISSIE
83a36ead10 ♻️ show success toast on resource policy update 2026-03-12 20:22:16 +01:00
Fred KISSIE
b61b74b0b5 💬 update text 2026-03-12 20:04:02 +01:00
Fred KISSIE
01b068c50f ♻️ do not edit tags if readonly 2026-03-12 18:53:18 +01:00
Fred KISSIE
fee44ce960 navigate to policy to edit 2026-03-12 18:52:13 +01:00
Fred KISSIE
1906504a86 update shared policy when selected 2026-03-12 18:35:50 +01:00
Fred KISSIE
36bcba332c 🚧 wip 2026-03-11 05:18:22 +01:00
Fred KISSIE
304ab1964c 🚧 wip 2026-03-11 04:21:55 +01:00
Fred KISSIE
b286096c7b 🌐 text 2026-03-11 03:47:31 +01:00
Fred KISSIE
a22a4b6e74 ♻️ mark forms as readonly 2026-03-11 03:47:15 +01:00
Fred KISSIE
9a680d2374 update resource should update policy 2026-03-11 03:46:40 +01:00
Fred KISSIE
f80e212b07 🚧 wip 2026-03-11 00:27:27 +01:00
Fred KISSIE
8a39b3fd45 🙈 do not include solo.yml to git 2026-03-10 18:55:12 +01:00
Fred KISSIE
61ec938b00 🚧 WIP 2026-03-10 18:54:26 +01:00
Fred KISSIE
6686de6788 ♻️ refactor 2026-03-10 17:48:17 +01:00
Fred KISSIE
79636cbb30 ♻️ delete default resource policy ID when deleting a resource 2026-03-10 17:38:19 +01:00
LunarECL
90d6178a0b Skip invalid Docker resources instead of failing entire blueprint (#1784) 2026-03-09 02:42:21 +09:00
Fred KISSIE
2fa1bc6cdc 🚧 wip 2026-03-07 03:55:30 +01:00
Fred KISSIE
c5f6d822ca ♻️ refactor auth info to use resource policies 2026-03-07 03:45:10 +01:00
Fred KISSIE
4de4bf9625 use resource policies for auth check 2026-03-07 03:35:26 +01:00
Fred KISSIE
5d956080f2 create default policy when creating a resource 2026-03-07 02:29:36 +01:00
Fred KISSIE
f8e18de2fc ♻️ prevent deleting resource policies if they have attached resources 2026-03-07 01:12:10 +01:00
Fred KISSIE
884482ec35 ♻️ delete resource policy endpoint 2026-03-06 23:57:23 +01:00
Fred KISSIE
9b43948fa4 delete resource policy endpoint 2026-03-06 22:39:44 +01:00
Fred KISSIE
bcd6cd99cc 🚧 wip 2026-03-06 04:37:57 +01:00
Fred KISSIE
37ceba6b81 💄 show attached resources in policy list 2026-03-06 04:36:12 +01:00
Fred KISSIE
dfe42e9016 ♻️ refactor 2026-03-06 04:03:40 +01:00
Fred KISSIE
38aa2dace8 ♻️ show list of resources on policy list 2026-03-06 04:03:25 +01:00
Fred KISSIE
136c3eff0c ♻️ padding bottom 2026-03-05 19:46:16 +01:00
Fred KISSIE
642999c8b1 ♻️ separate create form into multiple ones 2026-03-05 19:45:13 +01:00
Fred KISSIE
c5fc49b4fa 🚧 wip 2026-03-05 19:31:19 +01:00
Fred KISSIE
cd5a38b1eb 🚧 WIP: create policy form 2026-03-05 18:56:35 +01:00
Fred KISSIE
595842c2c9 finish create policy endpoint 2026-03-05 18:48:33 +01:00
Fred KISSIE
82d5276ade 🚧 wip: create resource policy 2026-03-05 18:24:04 +01:00
Fred KISSIE
51eb782831 🚧 wip 2026-03-05 18:14:46 +01:00
Fred KISSIE
de2980e1bc apply rules on resource policies 2026-03-05 18:13:30 +01:00
Fred KISSIE
8a3c0d9a08 ♻️ add openapi schema types 2026-03-05 17:51:55 +01:00
Fred KISSIE
1a5e9f1005 🚧 resource policy rules 2026-03-04 19:31:59 +01:00
Fred KISSIE
f42c013f33 ♻️ refactor 2026-03-04 17:41:55 +01:00
Fred KISSIE
42c9bda939 Merge branch 'dev' into feat/resource-policies 2026-03-04 16:46:33 +01:00
Fred KISSIE
cbce9fae3a 🚧 wip 2026-03-04 16:36:49 +01:00
Fred KISSIE
e44b15ecd5 set opt email whitelist 2026-03-04 01:54:50 +01:00
Fred KISSIE
7f6ca31757 🚧 Email whiteList for resource policy 2026-03-04 01:46:56 +01:00
Fred KISSIE
a1eb248474 🔨 remove docker compose mail 2026-03-04 01:10:48 +01:00
Fred KISSIE
be2b1fd1ce 🚧 wip: email whitelist 2026-03-03 20:26:17 +01:00
Fred KISSIE
20b65f549e Update resource policy pincode 2026-03-03 19:49:24 +01:00
Fred KISSIE
1dc8be373c 🚧 wip: add password 2026-03-03 18:54:35 +01:00
Fred KISSIE
22b2e6b3d4 🚧 wip: separating form sections 2026-03-03 18:41:04 +01:00
Fred KISSIE
89e7107a47 ♻️ use put and return 200 OK 2026-03-03 03:31:43 +01:00
Fred KISSIE
0a69131c38 ♻️ merge header auth & extended compability to one table 2026-03-03 03:27:02 +01:00
Fred KISSIE
590f2c29b3 🚧 prepare tables for auth methods 2026-03-03 03:20:03 +01:00
Fred KISSIE
0ddcce6fe1 🗃️ create resource policy specific tables for auth methods 2026-03-03 02:47:21 +01:00
Fred KISSIE
8a54fb7f23 🚧 auth methods 2026-03-03 02:11:05 +01:00
Fred KISSIE
5c280b024e update policy access control 2026-03-03 01:33:37 +01:00
Fred KISSIE
033cc62ce7 🚧 wip 2026-03-02 19:37:23 +01:00
Fred KISSIE
4c69b7a64e update policy access control 2026-03-02 19:26:51 +01:00
Fred KISSIE
e7ab9b3f37 🚧 wip 2026-03-02 18:32:08 +01:00
Fred KISSIE
3143662f82 Merge branch 'dev' into feat/resource-policies 2026-03-02 15:53:00 +01:00
Fred KISSIE
18964ba2a3 🚧 wip 2026-02-28 14:22:41 +01:00
Fred KISSIE
f862404c5c Merge branch 'dev' into feat/resource-policies 2026-02-28 01:17:51 +01:00
Fred KISSIE
c292578f80 Merge branch 'dev' into feat/resource-policies 2026-02-28 01:08:12 +01:00
Fred KISSIE
7b02d4104d 🚧 wip 2026-02-28 00:47:27 +01:00
Fred KISSIE
2ef5d90e13 ♻️ update policy in integration API 2026-02-27 04:24:33 +01:00
Fred KISSIE
d6a8021613 🚧 wip: update resource policy form 2026-02-27 04:21:20 +01:00
Fred KISSIE
c5231d37f6 🚧 wip 2026-02-26 19:20:15 +01:00
Fred KISSIE
4d803a40c9 🚧 wip 2026-02-25 06:00:19 +01:00
Fred KISSIE
1d709b551a create policy endpoitn 2026-02-24 06:31:43 +01:00
Fred KISSIE
335411de4c ♻️ create table for resource policies associations with users 2026-02-24 03:05:51 +01:00
Fred KISSIE
0e4abdf4b6 ♻️ usewatch 2026-02-20 02:06:23 +01:00
Fred KISSIE
267b40b73c 🚧 wip 2026-02-19 05:27:05 +01:00
Fred KISSIE
ba9a0c5e3c ♻️ refactor 2026-02-19 05:23:20 +01:00
Fred KISSIE
9e0b7ff0d7 ♻️ some other ux changes 2026-02-19 05:22:06 +01:00
Fred KISSIE
003bf7fdf3 🚸 hide otp, rules and resource rules config by default 2026-02-19 04:59:51 +01:00
Fred KISSIE
c3fdda026b ♻️ separate into diff components 2026-02-19 04:36:42 +01:00
Fred KISSIE
a53363d064 💄 include rules in create policy form 2026-02-19 03:23:54 +01:00
Fred KISSIE
ee21e1faa7 🚧 list authentication items from policy APIs 2026-02-18 05:08:42 +01:00
Fred KISSIE
e409a34a09 🚧 create policy form 2026-02-18 05:08:27 +01:00
Fred KISSIE
7177ab7f77 🚧 create resource policy table 2026-02-14 05:08:41 +01:00
Fred KISSIE
801f6fb661 🚚 move policies page to (private) folder 2026-02-14 05:03:40 +01:00
Fred KISSIE
805d82b8d9 policies table 2026-02-14 04:59:35 +01:00
Fred KISSIE
bd6d790495 Merge branch 'refactor/paginated-tables' into feat/resource-policies 2026-02-14 04:25:43 +01:00
Fred KISSIE
2305163474 🚧 wip 2026-02-14 03:24:01 +01:00
Fred KISSIE
dda53dcb16 Merge branch 'refactor/paginated-tables' into feat/resource-policies 2026-02-13 06:05:32 +01:00
Fred KISSIE
2c3e768867 🚧 wip: list resource endpoints finished 2026-02-13 05:54:45 +01:00
Fred KISSIE
8d682ed9ad 🚧 list policies endpoint + list policies table 2026-02-13 05:39:35 +01:00
Fred KISSIE
47fe497ca1 🚧 add sidebar item for policies 2026-02-13 05:39:16 +01:00
Fred KISSIE
4d5f364663 ♻️ use the correct types 2026-02-13 05:38:57 +01:00
Fred KISSIE
c3db8b972f ♻️ schema updates for policies 2026-02-13 05:36:42 +01:00
Fred KISSIE
cfced63ba1 Merge branch 'dev' into feat/resource-policies 2026-02-13 02:14:14 +01:00
Fred KISSIE
51aa55f963 revert changes already included in another PR 2026-02-13 00:25:00 +01:00
Fred KISSIE
e7df24841e ♻️ update sqlite DB 2026-02-12 03:50:30 +01:00
Fred KISSIE
e6fd4c32c4 ♻️ update DB 2026-02-12 03:50:09 +01:00
Fred KISSIE
f6590aedbd ♻️ add default sso: true to resource policy table 2026-02-12 03:22:24 +01:00
Fred KISSIE
3cb9e02533 ♻️ make resourcePolicyId non nullable 2026-02-12 02:56:45 +01:00
Fred KISSIE
4d792350ef 🗃️ add resource policy table 2026-02-12 02:53:04 +01:00
882 changed files with 86712 additions and 29338 deletions

View File

@@ -0,0 +1,5 @@
---
alwaysApply: true
---
When adding submit buttons, don't change the text of the button during the loading state. Text should stay static and you should use the loading prop on the button.

View File

@@ -0,0 +1,5 @@
---
alwaysApply: true
---
When creating UI for popup dialogs or modals, use the Credenza componennt. This component is mobile responsive and works on desktop and wraps the dialog component and sheet into one.

View File

@@ -0,0 +1,5 @@
---
alwaysApply: true
---
Always localize strings and use the `t` function to convert keys to strings. Add the keys to the en-us.json file. Never edit the other language files, as en-us.json is the single source of truth.

View File

@@ -0,0 +1,5 @@
---
alwaysApply: true
---
Don't write or edit migrations in `server/setup` unless specificall instructed to do so.

View File

@@ -0,0 +1,7 @@
---
description:
alwaysApply: true
---
Proxy resources = public resources
Private resources = client resources = site resources

View File

@@ -0,0 +1,7 @@
---
alwaysApply: true
---
When writing TypeScript:
Prefer to use types instead of interfaces.

View File

@@ -0,0 +1,5 @@
---
alwaysApply: true
---
When creating forms, use React form for validation and use Zod schemas.

View File

@@ -34,3 +34,5 @@ build.ts
tsconfig.json
Dockerfile*
drizzle.config.ts
allowedDevOrigins.json
scratch/

3
.github/FUNDING.yml vendored
View File

@@ -1,3 +0,0 @@
# These are supported funding model platforms
github: [fosrl]

View File

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

View File

@@ -1,52 +1,42 @@
version: 2
updates:
- package-ecosystem: "npm"
directory: "/"
schedule:
interval: "daily"
open-pull-requests-limit: 1
groups:
dev-patch-updates:
dependency-type: "development"
update-types:
- "patch"
dev-minor-updates:
dependency-type: "development"
update-types:
- "minor"
prod-patch-updates:
dependency-type: "production"
update-types:
- "patch"
prod-minor-updates:
dependency-type: "production"
update-types:
- "minor"
npm-dependencies:
patterns:
- "*"
- package-ecosystem: "docker"
directory: "/"
schedule:
interval: "daily"
open-pull-requests-limit: 1
groups:
patch-updates:
update-types:
- "patch"
minor-updates:
update-types:
- "minor"
docker-dependencies:
patterns:
- "*"
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
open-pull-requests-limit: 1
groups:
github-actions-dependencies:
patterns:
- "*"
- package-ecosystem: "gomod"
directory: "/install"
schedule:
interval: "daily"
open-pull-requests-limit: 1
groups:
patch-updates:
update-types:
- "patch"
minor-updates:
update-types:
- "minor"
go-install-dependencies:
patterns:
- "*"

View File

@@ -62,7 +62,7 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Monitor storage space
run: |
@@ -77,7 +77,7 @@ jobs:
fi
- name: Log in to Docker Hub
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4.4.0
with:
registry: docker.io
username: ${{ secrets.DOCKER_HUB_USERNAME }}
@@ -134,7 +134,7 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Monitor storage space
run: |
@@ -149,7 +149,7 @@ jobs:
fi
- name: Log in to Docker Hub
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4.4.0
with:
registry: docker.io
username: ${{ secrets.DOCKER_HUB_USERNAME }}
@@ -201,10 +201,10 @@ jobs:
timeout-minutes: 30
steps:
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Log in to Docker Hub
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4.4.0
with:
registry: docker.io
username: ${{ secrets.DOCKER_HUB_USERNAME }}
@@ -256,7 +256,7 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Extract tag name
id: get-tag
@@ -264,7 +264,7 @@ jobs:
shell: bash
- name: Install Go
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6.5.0
with:
go-version: 1.25
@@ -407,35 +407,27 @@ jobs:
shell: bash
- name: Login to GitHub Container Registry (for cosign)
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
uses: docker/login-action@af1e73f918a031802d376d3c8bbc3fe56130a9b0 # v4.4.0
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Install cosign
# cosign is used to sign and verify container images (key and keyless)
uses: sigstore/cosign-installer@cad07c2e89fa2edd6e2d7bab4c1aa38e53f76003 # v4.1.1
# cosign is used to sign container images using keyless (OIDC) signing
uses: sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6 # v4.1.2
with:
cosign-release: v3.0.6
- name: Dual-sign and verify (GHCR & Docker Hub)
# Sign each image by digest using keyless (OIDC) and key-based signing,
# then verify both the public key signature and the keyless OIDC signature.
- name: Sign (GHCR, keyless)
# Sign each GHCR image by digest using keyless (OIDC) signing via Sigstore/Rekor.
# Signatures are stored in the registry alongside the image.
env:
TAG: ${{ env.TAG }}
COSIGN_PRIVATE_KEY: ${{ secrets.COSIGN_PRIVATE_KEY }}
COSIGN_PASSWORD: ${{ secrets.COSIGN_PASSWORD }}
COSIGN_PUBLIC_KEY: ${{ secrets.COSIGN_PUBLIC_KEY }}
COSIGN_YES: "true"
run: |
set -euo pipefail
issuer="https://token.actions.githubusercontent.com"
id_regex="^https://github.com/${{ github.repository }}/.+" # accept this repo (all workflows/refs)
# Track failures
FAILED_TAGS=()
SUCCESSFUL_TAGS=()
# Determine if this is an RC release
IS_RC="false"
if [[ "$TAG" == *"-rc."* ]]; then
@@ -463,95 +455,47 @@ jobs:
)
fi
# 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
FAILED_TAGS=()
SUCCESSFUL_TAGS=()
# 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}"
for IMAGE_TAG in "${IMAGE_TAGS[@]}"; do
echo "Processing ${GHCR_IMAGE}:${IMAGE_TAG}"
TAG_FAILED=false
echo "==> cosign sign (keyless) --recursive ${REF}"
cosign sign --recursive "${REF}"
(
set -e
DIGEST="$(skopeo inspect --retry-times 3 docker://${GHCR_IMAGE}:${IMAGE_TAG} | jq -r '.Digest')"
REF="${GHCR_IMAGE}@${DIGEST}"
echo "Resolved digest: ${REF}"
echo "==> cosign sign (key) --recursive ${REF}"
cosign sign --key env://COSIGN_PRIVATE_KEY --recursive "${REF}"
echo "==> cosign sign (keyless) --recursive ${REF}"
cosign sign --recursive "${REF}"
) || TAG_FAILED=true
# 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
if [ "$TAG_FAILED" = "true" ]; then
echo "⚠️ WARNING: Failed to sign ${GHCR_IMAGE}:${IMAGE_TAG}"
FAILED_TAGS+=("${GHCR_IMAGE}:${IMAGE_TAG}")
else
echo "✓ Successfully signed ${GHCR_IMAGE}:${IMAGE_TAG}"
SUCCESSFUL_TAGS+=("${GHCR_IMAGE}:${IMAGE_TAG}")
fi
done
# Report summary
echo ""
echo "=========================================="
echo "Sign and Verify Summary"
echo "Sign 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"
echo "⚠️ WARNING: Some tags failed to sign, but continuing anyway"
else
echo "✓ All images signed and verified successfully!"
echo "✓ All images signed successfully!"
fi
shell: bash

View File

@@ -21,10 +21,10 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Set up Node.js
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: '24'

View File

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

View File

@@ -1,39 +0,0 @@
name: Restart Runners
on:
schedule:
- cron: '0 0 */7 * *'
permissions:
id-token: write
contents: read
jobs:
ec2-maintenance-prod:
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 instance
run: |
aws ec2 start-instances --instance-ids ${{ secrets.EC2_INSTANCE_ID_ARM_RUNNER }}
aws ec2 start-instances --instance-ids ${{ secrets.EC2_INSTANCE_ID_AMD_RUNNER }}
echo "EC2 instances started"
- name: Wait
run: sleep 600
- name: Stop EC2 instance
run: |
aws ec2 stop-instances --instance-ids ${{ secrets.EC2_INSTANCE_ID_ARM_RUNNER }}
aws ec2 stop-instances --instance-ids ${{ secrets.EC2_INSTANCE_ID_AMD_RUNNER }}
echo "EC2 instances stopped"

View File

@@ -1,160 +0,0 @@
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"

View File

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

View File

@@ -14,10 +14,10 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Install Node
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: '24'
@@ -62,7 +62,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Build Docker image sqlite
run: make dev-build-sqlite
@@ -71,7 +71,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Build Docker image pg
run: make dev-build-pg

6
.gitignore vendored
View File

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

View File

@@ -18,5 +18,8 @@
"[json]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"editor.formatOnSave": true
"editor.formatOnSave": true,
"cSpell.words": [
"nessicary"
]
}

View File

@@ -41,7 +41,7 @@
</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 with NAT traversal, all with granular access controls.
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
@@ -107,7 +107,7 @@ the docs to illustrate some basic ideas.
## 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).
Pangolin is dual licensed under the AGPL-3 and the [Fossorial Commercial License](https://pangolin.net/fcl). For inquiries about commercial licensing, please contact us at [contact@pangolin.net](mailto:contact@pangolin.net).
## Contributions

View File

@@ -0,0 +1,28 @@
import { CommandModule } from "yargs";
import { db, certificates } from "@server/db";
type ClearCertificatesArgs = {};
export const clearCertificates: CommandModule<{}, ClearCertificatesArgs> = {
command: "clear-certificates",
describe: "Delete all entries from the certificates table",
builder: (yargs) => {
return yargs;
},
handler: async (argv: {}) => {
try {
console.log("Clearing all certificates from the database...");
const deleted = await db.delete(certificates).returning();
console.log(
`Deleted ${deleted.length} certificate(s) from the database`
);
process.exit(0);
} catch (error) {
console.error("Error:", error);
process.exit(1);
}
}
};

View File

@@ -0,0 +1,60 @@
import { CommandModule } from "yargs";
import { db, users } from "@server/db";
import { eq } from "drizzle-orm";
/**
* Disable 2FA for a user by email address.
*/
type DisableUser2faArgs = {
email: string;
};
export const disableUser2fa: CommandModule<{}, DisableUser2faArgs> = {
command: "disable-user-2fa",
describe: "Disable 2FA for a user (sets twoFactorEnabled=false, clears secret)",
builder: (yargs) => {
return yargs.option("email", {
type: "string",
demandOption: true,
describe: "User email address"
});
},
handler: async (argv: { email: string }) => {
try {
const { email } = argv;
console.log(`Looking for user with email: ${email}`);
// Find the user by email
const [user] = await db
.select()
.from(users)
.where(eq(users.email, email))
.limit(1);
if (!user) {
console.error(`User with email '${email}' not found`);
process.exit(1);
}
if (!user.twoFactorEnabled) {
console.log(`2FA is already disabled for user '${email}'.`);
process.exit(0);
}
// Update user: disable 2FA and clear secret
await db.update(users)
.set({
twoFactorEnabled: false,
twoFactorSecret: null,
twoFactorSetupRequested: false
})
.where(eq(users.userId, user.userId));
console.log(`2FA disabled for user '${email}'.`);
process.exit(0);
} catch (error) {
console.error("Error disabling 2FA:", error);
process.exit(1);
}
}
};

View File

@@ -1,5 +1,5 @@
import { CommandModule } from "yargs";
import { db, idpOidcConfig, licenseKey } from "@server/db";
import { db, idpOidcConfig, licenseKey, certificates, eventStreamingDestinations, alertWebhookActions } from "@server/db";
import { encrypt, decrypt } from "@server/lib/crypto";
import { configFilePath1, configFilePath2 } from "@server/lib/consts";
import { eq } from "drizzle-orm";
@@ -129,9 +129,15 @@ export const rotateServerSecret: CommandModule<
console.log("\nReading encrypted data from database...");
const idpConfigs = await db.select().from(idpOidcConfig);
const licenseKeys = await db.select().from(licenseKey);
const certs = await db.select().from(certificates);
const streamingDestinations = await db.select().from(eventStreamingDestinations);
const webhookActions = await db.select().from(alertWebhookActions);
console.log(`Found ${idpConfigs.length} OIDC IdP configuration(s)`);
console.log(`Found ${licenseKeys.length} license key(s)`);
console.log(`Found ${certs.length} certificate(s)`);
console.log(`Found ${streamingDestinations.length} event streaming destination(s)`);
console.log(`Found ${webhookActions.length} alert webhook action(s)`);
// Prepare all decrypted and re-encrypted values
console.log("\nDecrypting and re-encrypting values...");
@@ -149,8 +155,27 @@ export const rotateServerSecret: CommandModule<
encryptedInstanceId: string;
};
type CertUpdate = {
certId: number;
encryptedCertFile: string | null;
encryptedKeyFile: string | null;
};
type StreamingDestinationUpdate = {
destinationId: number;
encryptedConfig: string;
};
type WebhookActionUpdate = {
webhookActionId: number;
encryptedConfig: string;
};
const idpUpdates: IdpUpdate[] = [];
const licenseKeyUpdates: LicenseKeyUpdate[] = [];
const certUpdates: CertUpdate[] = [];
const streamingDestinationUpdates: StreamingDestinationUpdate[] = [];
const webhookActionUpdates: WebhookActionUpdate[] = [];
// Process idpOidcConfig entries
for (const idpConfig of idpConfigs) {
@@ -217,6 +242,70 @@ export const rotateServerSecret: CommandModule<
}
}
// Process certificate entries
for (const cert of certs) {
try {
const encryptedCertFile = cert.certFile
? encrypt(decrypt(cert.certFile, oldSecret), newSecret)
: null;
const encryptedKeyFile = cert.keyFile
? encrypt(decrypt(cert.keyFile, oldSecret), newSecret)
: null;
certUpdates.push({
certId: cert.certId,
encryptedCertFile,
encryptedKeyFile
});
} catch (error) {
console.error(
`Error processing certificate ${cert.certId} (${cert.domain}):`,
error
);
throw error;
}
}
// Process eventStreamingDestinations entries
for (const dest of streamingDestinations) {
try {
const decryptedConfig = decrypt(dest.config, oldSecret);
const encryptedConfig = encrypt(decryptedConfig, newSecret);
streamingDestinationUpdates.push({
destinationId: dest.destinationId,
encryptedConfig
});
} catch (error) {
console.error(
`Error processing event streaming destination ${dest.destinationId}:`,
error
);
throw error;
}
}
// Process alertWebhookActions entries
for (const webhook of webhookActions) {
try {
if (webhook.config == null) continue;
const decryptedConfig = decrypt(webhook.config, oldSecret);
const encryptedConfig = encrypt(decryptedConfig, newSecret);
webhookActionUpdates.push({
webhookActionId: webhook.webhookActionId,
encryptedConfig
});
} catch (error) {
console.error(
`Error processing alert webhook action ${webhook.webhookActionId}:`,
error
);
throw error;
}
}
// Perform all database updates in a single transaction
console.log("\nUpdating database in transaction...");
await db.transaction(async (trx) => {
@@ -250,10 +339,50 @@ export const rotateServerSecret: CommandModule<
instanceId: update.encryptedInstanceId
});
}
// Update certificate entries
for (const update of certUpdates) {
await trx
.update(certificates)
.set({
certFile: update.encryptedCertFile,
keyFile: update.encryptedKeyFile
})
.where(eq(certificates.certId, update.certId));
}
// Update event streaming destination entries
for (const update of streamingDestinationUpdates) {
await trx
.update(eventStreamingDestinations)
.set({ config: update.encryptedConfig })
.where(
eq(
eventStreamingDestinations.destinationId,
update.destinationId
)
);
}
// Update alert webhook action entries
for (const update of webhookActionUpdates) {
await trx
.update(alertWebhookActions)
.set({ config: update.encryptedConfig })
.where(
eq(
alertWebhookActions.webhookActionId,
update.webhookActionId
)
);
}
});
console.log(`Rotated ${idpUpdates.length} OIDC IdP configuration(s)`);
console.log(`Rotated ${licenseKeyUpdates.length} license key(s)`);
console.log(`Rotated ${certUpdates.length} certificate(s)`);
console.log(`Rotated ${streamingDestinationUpdates.length} event streaming destination(s)`);
console.log(`Rotated ${webhookActionUpdates.length} alert webhook action(s)`);
// Update config file with new secret
console.log("\nUpdating config file...");
@@ -270,6 +399,9 @@ export const rotateServerSecret: CommandModule<
console.log(`\nSummary:`);
console.log(` - OIDC IdP configurations: ${idpUpdates.length}`);
console.log(` - License keys: ${licenseKeyUpdates.length}`);
console.log(` - Certificates: ${certUpdates.length}`);
console.log(` - Event streaming destinations: ${streamingDestinationUpdates.length}`);
console.log(` - Alert webhook actions: ${webhookActionUpdates.length}`);
console.log(
`\n IMPORTANT: Restart the server for the new secret to take effect.`
);

View File

@@ -0,0 +1,85 @@
import { CommandModule } from "yargs";
import { db, users } from "@server/db";
import { eq } from "drizzle-orm";
type SetServerAdminArgs = {
email: string;
remove: boolean;
};
export const setServerAdmin: CommandModule<{}, SetServerAdminArgs> = {
command: "set-server-admin",
describe: "Add or remove server admin by email address",
builder: (yargs) => {
return yargs
.option("email", {
type: "string",
demandOption: true,
describe: "User email address"
})
.option("remove", {
type: "boolean",
default: false,
describe: "Remove server admin status from the user"
});
},
handler: async (argv: SetServerAdminArgs) => {
try {
const email = argv.email.trim().toLowerCase();
const [user] = await db
.select()
.from(users)
.where(eq(users.email, email))
.limit(1);
if (!user) {
console.error(`User with email '${email}' not found`);
process.exit(1);
}
if (argv.remove) {
if (!user.serverAdmin) {
console.log(`User '${email}' is not a server admin`);
process.exit(0);
}
const serverAdmins = await db
.select()
.from(users)
.where(eq(users.serverAdmin, true));
if (serverAdmins.length <= 1) {
console.error(
"Cannot remove server admin: at least one server admin must exist"
);
process.exit(1);
}
await db
.update(users)
.set({ serverAdmin: false })
.where(eq(users.userId, user.userId));
console.log(`Server admin status removed from user '${email}'`);
process.exit(0);
}
if (user.serverAdmin) {
console.log(`User '${email}' is already a server admin`);
process.exit(0);
}
await db
.update(users)
.set({ serverAdmin: true })
.where(eq(users.userId, user.userId));
console.log(`User '${email}' has been marked as a server admin`);
process.exit(0);
} catch (error) {
console.error("Error:", error);
process.exit(1);
}
}
};

View File

@@ -9,6 +9,9 @@ import { rotateServerSecret } from "./commands/rotateServerSecret";
import { clearLicenseKeys } from "./commands/clearLicenseKeys";
import { deleteClient } from "./commands/deleteClient";
import { generateOrgCaKeys } from "./commands/generateOrgCaKeys";
import { clearCertificates } from "./commands/clearCertificates";
import { disableUser2fa } from "./commands/disableUser2fa";
import { setServerAdmin } from "./commands/setServerAdmin";
yargs(hideBin(process.argv))
.scriptName("pangctl")
@@ -19,5 +22,8 @@ yargs(hideBin(process.argv))
.command(clearLicenseKeys)
.command(deleteClient)
.command(generateOrgCaKeys)
.command(clearCertificates)
.command(disableUser2fa)
.command(setServerAdmin)
.demandCommand()
.help().argv;

View File

@@ -41,7 +41,7 @@ services:
- 80:80 # Port for traefik because of the network_mode
traefik:
image: traefik:v3.6
image: traefik:v3.7
container_name: traefik
restart: unless-stopped
network_mode: service:gerbil # Ports appear on the gerbil service

12
compose.mailpit.yaml Normal file
View File

@@ -0,0 +1,12 @@
services:
mailer:
image: axllent/mailpit
ports:
- 8025:8025
- 1025:1025
volumes:
- mailpit-storage:/data
environment:
- MP_DATABASE=/data/mailpit.db
volumes:
mailpit-storage:

View File

@@ -1,54 +1,47 @@
api:
insecure: true
dashboard: true
providers:
http:
endpoint: "http://pangolin:3001/api/v1/traefik-config"
pollInterval: "5s"
endpoint: http://pangolin:3001/api/v1/traefik-config
pollInterval: 5s
file:
filename: "/etc/traefik/dynamic_config.yml"
filename: /etc/traefik/dynamic_config.yml
experimental:
plugins:
badger:
moduleName: "github.com/fosrl/badger"
version: "{{.BadgerVersion}}"
moduleName: github.com/fosrl/badger
version: v1.4.1
log:
level: "INFO"
format: "common"
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"
email: '{{.LetsEncryptEmail}}'
storage: /letsencrypt/acme.json
caServer: https://acme-v02.api.letsencrypt.org/directory
entryPoints:
web:
address: ":80"
address: ':80'
websecure:
address: ":443"
address: ':443'
transport:
respondingTimeouts:
readTimeout: "30m"
readTimeout: 30m
http:
tls:
certResolver: "letsencrypt"
certResolver: letsencrypt
encodedCharacters:
allowEncodedSlash: true
allowEncodedQuestionMark: true
serversTransport:
insecureSkipVerify: true
ping:
entryPoint: "web"
entryPoint: web

View File

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

View File

@@ -22,7 +22,8 @@ server:
methods: ["GET", "POST", "PUT", "DELETE", "PATCH"]
allowed_headers: ["X-CSRF-Token", "Content-Type"]
credentials: false
{{if .EnableGeoblocking}}maxmind_db_path: "./config/GeoLite2-Country.mmdb"{{end}}
{{if .EnableMaxMind}}maxmind_db_path: "./config/GeoLite2-Country.mmdb"{{end}}
{{if .EnableMaxMind}}maxmind_asn_path: "./config/GeoLite2-ASN.mmdb"{{end}}
{{if .EnableEmail}}
email:
smtp_host: "{{.EmailSMTPHost}}"
@@ -36,3 +37,6 @@ flags:
disable_signup_without_invite: true
disable_user_create_org: false
allow_raw_resources: true
{{if .IsPostgreSQL}}postgres:
connection_string: postgresql://pangolin:{{.IsPostgreSQLPass}}@postgres:5432/pangolin{{end}}

View File

@@ -1,15 +1,23 @@
name: pangolin
services:
pangolin:
image: docker.io/fosrl/pangolin:{{if .IsEnterprise}}ee-{{end}}{{.PangolinVersion}}
image: docker.io/fosrl/pangolin:{{if .IsEnterprise}}ee-{{end}}{{if .IsPostgreSQL}}postgresql-{{end}}{{.PangolinVersion}}
container_name: pangolin
restart: unless-stopped
deploy:
resources:
limits:
memory: 1g
memory: 2g
reservations:
memory: 256m
memory: 512m
{{if or .IsPostgreSQL .IsRedis}}depends_on:
{{if .IsPostgreSQL}}postgres:
condition: service_healthy{{end}}
{{if .IsRedis}}redis:
condition: service_healthy{{end}}
networks:
- default
- backend{{end}}
volumes:
- ./config:/app/config
healthcheck:
@@ -17,8 +25,8 @@ services:
interval: "10s"
timeout: "10s"
retries: 15
{{if .InstallGerbil}}
gerbil:
{{if .InstallGerbil}}gerbil:
image: docker.io/fosrl/gerbil:{{.GerbilVersion}}
container_name: gerbil
restart: unless-stopped
@@ -39,17 +47,16 @@ services:
- 21820:21820/udp
- 443:443
- 443:443/udp # For http3 QUIC if desired
- 80:80
{{end}}
- 80:80{{end}}
traefik:
image: docker.io/traefik:v3.6
image: docker.io/traefik:v3.7
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
{{end}}
- 80:80{{end}}
depends_on:
pangolin:
condition: service_healthy
@@ -60,8 +67,50 @@ services:
- ./config/letsencrypt:/letsencrypt # Volume to store the Let's Encrypt certificates
- ./config/traefik/logs:/var/log/traefik # Volume to store Traefik logs
{{if .IsPostgreSQL}}postgres:
image: postgres:18
container_name: postgres
restart: unless-stopped
environment:
POSTGRES_USER: pangolin
POSTGRES_PASSWORD: {{.IsPostgreSQLPass}}
POSTGRES_DB: pangolin
volumes:
- ./postgres18:/var/lib/postgresql
healthcheck:
test: ["CMD-SHELL", "pg_isready -U pangolin"]
interval: 10s
timeout: 5s
retries: 5
networks:
- backend{{end}}
{{if .IsRedis}}redis:
image: redis:8-trixie
container_name: redis
restart: unless-stopped
command: >
redis-server
--save 3600 1000
--appendonly yes
--requirepass {{.IsRedisPass}}
volumes:
- ./redis8:/data
healthcheck:
test: ["CMD", "redis-cli", "-a", "{{.IsRedisPass}}", "ping"]
interval: 10s
timeout: 3s
retries: 3
start_period: 10s
networks:
- backend{{end}}
networks:
default:
driver: bridge
name: pangolin
name: pangolin_frontend
{{if .EnableIPv6}} enable_ipv6: true{{end}}
{{if or .IsPostgreSQL .IsRedis}} backend:
driver: bridge
name: pangolin_backend
internal: true{{end}}

View File

@@ -0,0 +1,4 @@
{{if .IsRedis}}redis:
host: "redis"
port: 6379
password: "{{.IsRedisPass}}"{{end}}

View File

@@ -6,12 +6,13 @@ import (
"log"
"os"
"os/exec"
"path/filepath"
"strings"
"gopkg.in/yaml.v3"
)
func installCrowdsec(config Config) error {
func installCrowdsec(config Config, installDir string) error {
if err := stopContainers(config.InstallationContainerType); err != nil {
return fmt.Errorf("failed to stop containers: %v", err)
@@ -40,6 +41,8 @@ func installCrowdsec(config Config) error {
os.Exit(1)
}
setupTraefikLogRotate(installDir)
if err := copyDockerService("config/crowdsec/docker-compose.yml", "docker-compose.yml", "crowdsec"); err != nil {
fmt.Printf("Error copying docker service: %v\n", err)
os.Exit(1)
@@ -208,3 +211,69 @@ func CheckAndAddCrowdsecDependency(composePath string) error {
fmt.Println("Added dependency of crowdsec to traefik")
return nil
}
// setupTraefikLogRotate writes a logrotate config for the Traefik access log
// that CrowdSec depends on. This is only needed when CrowdSec is installed
// because the default Pangolin install does not enable Traefik access logs.
//
// copytruncate is used so Traefik does not need to be restarted or sent a
// signal after rotation — it keeps writing to the same file descriptor while
// the rotated copy is made and the original is truncated in place.
func setupTraefikLogRotate(installDir string) {
const logrotateDir = "/etc/logrotate.d"
const logrotateFile = "/etc/logrotate.d/pangolin-traefik"
logPath := filepath.Join(installDir, "config/traefik/logs/access.log")
if os.Geteuid() != 0 {
fmt.Println("\n[logrotate] Skipping automatic logrotate setup: not running as root.")
fmt.Println("[logrotate] To prevent unbounded growth of the Traefik access log used by CrowdSec,")
fmt.Println("[logrotate] create the file /etc/logrotate.d/pangolin-traefik manually with:")
printLogrotateConfig(logPath)
return
}
config := fmt.Sprintf(`# Logrotate config for Traefik access logs used by CrowdSec.
# Generated by the Pangolin installer. Safe to edit.
%s {
daily
rotate 7
compress
delaycompress
missingok
notifempty
copytruncate
}
`, logPath)
if err := os.MkdirAll(logrotateDir, 0755); err != nil {
fmt.Printf("[logrotate] Warning: could not create %s: %v\n", logrotateDir, err)
return
}
if err := os.WriteFile(logrotateFile, []byte(config), 0644); err != nil {
fmt.Printf("[logrotate] Warning: could not write %s: %v\n", logrotateFile, err)
fmt.Println("[logrotate] Set it up manually:")
printLogrotateConfig(logPath)
return
}
fmt.Printf("[logrotate] Wrote logrotate config to %s\n", logrotateFile)
fmt.Println("[logrotate] Traefik access logs will be rotated daily, keeping 7 compressed copies.")
}
// printLogrotateConfig prints a logrotate config block to stdout so users can
// set it up manually when the installer cannot write to /etc.
func printLogrotateConfig(logPath string) {
fmt.Printf(`
%s {
daily
rotate 7
compress
delaycompress
missingok
notifempty
copytruncate
}
`, logPath)
}

View File

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

View File

@@ -69,10 +69,10 @@ golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8=
golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI=
golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/term v0.42.0 h1:UiKe+zDFmJobeJ5ggPwOshJIVt6/Ft0rcfrXZDLWAWY=
golang.org/x/term v0.42.0/go.mod h1:Dq/D+snpsbazcBG5+F9Q1n2rXV8Ma+71xEjTRufARgY=
golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw=
golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/term v0.44.0 h1:0rLvDRCtNj0gZkyIXhCyOb2OAzEhLVqc4B+hrsBhrmc=
golang.org/x/term v0.44.0/go.mod h1:7ze4MdzUzLXpSAoFP1H0bOI9aXDqveSvatT5vKcFh2Y=
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=

View File

@@ -4,6 +4,7 @@ import (
"crypto/rand"
"embed"
"encoding/base64"
"flag"
"fmt"
"io"
"io/fs"
@@ -53,9 +54,13 @@ type Config struct {
InstallGerbil bool
TraefikBouncerKey string
DoCrowdsecInstall bool
EnableGeoblocking bool
EnableMaxMind bool
Secret string
IsEnterprise bool
IsPostgreSQL bool
IsPostgreSQLPass string
IsRedis bool
IsRedisPass string
}
type SupportedContainer string
@@ -66,8 +71,14 @@ const (
Undefined SupportedContainer = "undefined"
)
var redisFlag *bool
func main() {
crowdsecFlag := flag.Bool("crowdsec", false, "Enable the CrowdSec installation prompt")
redisFlag = flag.Bool("redis", false, "Install Redis as cacheing solution. Required for HA. Not required for the Enterprise version.")
flag.Parse()
// print a banner about prerequisites - opening port 80, 443, 51820, and 21820 on the VPS and firewall and pointing your domain to the VPS IP with a records. Docs are at http://localhost:3000/Getting%20Started/dns-networking
fmt.Println("Welcome to the Pangolin installer!")
@@ -119,11 +130,11 @@ func main() {
fmt.Println("\nConfiguration files created successfully!")
// Download MaxMind database if requested
if config.EnableGeoblocking {
fmt.Println("\n=== Downloading MaxMind Database ===")
// Download MaxMind Country / ASN database if requested
if config.EnableMaxMind {
fmt.Println("\n=== Downloading MaxMind Country and ASN Databases ===")
if err := downloadMaxMindDatabase(); err != nil {
fmt.Printf("Error downloading MaxMind database: %v\n", err)
fmt.Printf("Error downloading MaxMind databases: %v\n", err)
fmt.Println("You can download it manually later if needed.")
}
}
@@ -184,15 +195,15 @@ func main() {
fmt.Println("\n=== MaxMind Database Update ===")
if _, err := os.Stat("config/GeoLite2-Country.mmdb"); err == nil {
fmt.Println("MaxMind GeoLite2 Country database found.")
if readBool("Would you like to update the MaxMind database to the latest version?", false) {
if readBool("Would you like to update the MaxMind databases (Country and ASN) to the latest version?", false) {
if err := downloadMaxMindDatabase(); err != nil {
fmt.Printf("Error updating MaxMind database: %v\n", err)
fmt.Println("You can try updating it manually later if needed.")
}
}
} else {
fmt.Println("MaxMind GeoLite2 Country database not found.")
if readBool("Would you like to download the MaxMind GeoLite2 database for geoblocking functionality?", false) {
fmt.Println("MaxMind GeoLite2 Country and ASN databases not found.")
if readBool("Would you like to download the MaxMind GeoLite2 databases for blocking functionality?", false) {
if err := downloadMaxMindDatabase(); err != nil {
fmt.Printf("Error downloading MaxMind database: %v\n", err)
fmt.Println("You can try downloading it manually later if needed.")
@@ -200,13 +211,15 @@ func main() {
// Now you need to update your config file accordingly to enable geoblocking
fmt.Print("Please remember to update your config/config.yml file to enable geoblocking! \n\n")
// add maxmind_db_path: "./config/GeoLite2-Country.mmdb" under server
fmt.Println("Add the following line under the 'server' section:")
// add maxmind_asn_path: "./config/GeoLite2-ASN.mmdb" under server
fmt.Println("Add the following lines under the 'server' section:")
fmt.Println(" maxmind_db_path: \"./config/GeoLite2-Country.mmdb\"")
fmt.Println(" maxmind_asn_path: \"./config/GeoLite2-ASN.mmdb\"")
}
}
}
if !checkIsCrowdsecInstalledInCompose() {
if *crowdsecFlag && !checkIsCrowdsecInstalledInCompose() {
fmt.Println("\n=== CrowdSec Install ===")
// check if crowdsec is installed
if readBool("Would you like to install CrowdSec?", false) {
@@ -259,7 +272,7 @@ func main() {
}
config.DoCrowdsecInstall = true
err := installCrowdsec(config)
err := installCrowdsec(config, installDir)
if err != nil {
fmt.Printf("Error installing CrowdSec: %v\n", err)
return
@@ -480,6 +493,17 @@ func collectUserInput() Config {
fmt.Println("\n=== Basic Configuration ===")
config.IsEnterprise = readBoolNoDefault("Do you want to install the Enterprise version of Pangolin? The EE is free for personal use or for businesses making less than 100k USD annually.")
if config.IsEnterprise {
if *redisFlag {
config.IsRedis = true
config.IsRedisPass = readPassword("Enter a unique password for the Redis service.")
}
}
config.IsPostgreSQL = readBool("Do you want to use PostgreSQL (not recommended for most users)?", false)
if config.IsPostgreSQL {
config.IsPostgreSQLPass = readPassword("Enter a unique password for the PostgreSQL pangolin user.")
}
config.BaseDomain = readString("Enter your base domain (no subdomain e.g. example.com)", "")
@@ -523,7 +547,7 @@ func collectUserInput() Config {
fmt.Println("\n=== Advanced Configuration ===")
config.EnableIPv6 = readBool("Is your server IPv6 capable?", true)
config.EnableGeoblocking = readBool("Do you want to download the MaxMind GeoLite2 database for geoblocking functionality?", true)
config.EnableMaxMind = readBool("Do you want to download the MaxMind GeoLite2 Country and ASN databases for blocking functionality?", true)
if config.DashboardDomain == "" {
fmt.Println("Error: Dashboard Domain name is required")
@@ -776,29 +800,42 @@ func checkPortsAvailable(port int) error {
}
func downloadMaxMindDatabase() error {
fmt.Println("Downloading MaxMind GeoLite2 Country database...")
fmt.Println("Downloading MaxMind GeoLite2 Country and ASN databases...")
// Download the GeoLite2 Country database
// Download the GeoLite2 Country databases
if err := run("curl", "-L", "-o", "GeoLite2-Country.tar.gz",
"https://github.com/GitSquared/node-geolite2-redist/raw/refs/heads/master/redist/GeoLite2-Country.tar.gz"); err != nil {
return fmt.Errorf("failed to download GeoLite2 database: %v", err)
return fmt.Errorf("failed to download GeoLite2 Country database: %v", err)
}
if err := run("curl", "-L", "-o", "GeoLite2-ASN.tar.gz",
"https://github.com/GitSquared/node-geolite2-redist/raw/refs/heads/master/redist/GeoLite2-ASN.tar.gz"); err != nil {
return fmt.Errorf("failed to download GeoLite2 ASN database: %v", err)
}
// Extract the database
// Extract the Country database
if err := run("tar", "-xzf", "GeoLite2-Country.tar.gz"); err != nil {
return fmt.Errorf("failed to extract GeoLite2 database: %v", err)
return fmt.Errorf("failed to extract GeoLite2 Country database: %v", err)
}
if err := run("tar", "-xzf", "GeoLite2-ASN.tar.gz"); err != nil {
return fmt.Errorf("failed to extract GeoLite2 ASN database: %v", err)
}
// Find the .mmdb file and move it to the config directory
if err := run("bash", "-c", "mv GeoLite2-Country_*/GeoLite2-Country.mmdb config/"); err != nil {
return fmt.Errorf("failed to move GeoLite2 database to config directory: %v", err)
return fmt.Errorf("failed to move GeoLite2 Country database to config directory: %v", err)
}
if err := run("bash", "-c", "mv GeoLite2-ASN_*/GeoLite2-ASN.mmdb config/"); err != nil {
return fmt.Errorf("failed to move GeoLite2 ASN database to config directory: %v", err)
}
// Clean up the downloaded files
if err := run("rm", "-rf", "GeoLite2-Country.tar.gz", "GeoLite2-Country_*"); err != nil {
fmt.Printf("Warning: failed to clean up temporary files: %v\n", err)
if err := run("sh", "-c", "rm -rf GeoLite2-Country.tar.gz GeoLite2-Country_*"); err != nil {
fmt.Printf("Warning: failed to clean up temporary country files: %v\n", err)
}
if err := run("sh", "-c", "rm -rf GeoLite2-ASN.tar.gz GeoLite2-ASN_*"); err != nil {
fmt.Printf("Warning: failed to clean up temporary ASN files: %v\n", err)
}
fmt.Println("MaxMind GeoLite2 Country database downloaded successfully!")
fmt.Println("MaxMind GeoLite2 Country and ASN database downloaded successfully!")
return nil
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

3687
messages/da-DK.json Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -152,8 +152,8 @@
"shareErrorSelectResource": "請選擇一個資源",
"proxyResourceTitle": "管理公開資源",
"proxyResourceDescription": "建立和管理可透過網頁瀏覽器公開存取的資源",
"proxyResourcesBannerTitle": "基於網頁的公開存取",
"proxyResourcesBannerDescription": "公開資源是任何人都可以透過網頁瀏覽器存取的 HTTPS 或 TCP/UDP 代理。與私有資源不同,它們不需要客戶端軟體,並且可以包含基於身份和情境感知的存取策略。",
"publicResourcesBannerTitle": "基於網頁的公開存取",
"publicResourcesBannerDescription": "公開資源是任何人都可以透過網頁瀏覽器存取的 HTTPS 或 TCP/UDP 代理。與私有資源不同,它們不需要客戶端軟體,並且可以包含基於身份和情境感知的存取策略。",
"clientResourceTitle": "管理私有資源",
"clientResourceDescription": "建立和管理只能透過已連接的客戶端存取的資源",
"privateResourcesBannerTitle": "零信任私有存取",
@@ -489,7 +489,7 @@
"createdAt": "創建於",
"proxyErrorInvalidHeader": "無效的自訂主機 Header。使用域名格式或將空保存為取消自訂 Header。",
"proxyErrorTls": "無效的 TLS 伺服器名稱。使用域名格式,或保存空以刪除 TLS 伺服器名稱。",
"proxyEnableSSL": "啟用 SSL",
"proxyEnableSSL": "啟用 TLS",
"proxyEnableSSLDescription": "啟用 SSL/TLS 加密以確保您目標的 HTTPS 連接。",
"target": "目標",
"configureTarget": "配置目標",
@@ -1763,7 +1763,7 @@
"description": "更可靠、維護成本更低的自架 Pangolin 伺服器,並附帶額外的附加功能",
"introTitle": "託管式自架 Pangolin",
"introDescription": "這是一種部署選擇,為那些希望簡潔和額外可靠的人設計,同時仍然保持他們的數據的私密性和自我託管性。",
"introDetail": "通過此選項,您仍然運行您自己的 Pangolin 節點 - - 您的隧道、SSL 終止,並且流量在您的伺服器上保持所有狀態。 不同之處在於,管理和監測是通過我們的雲層儀錶板進行的,該儀錶板開啟了一些好處:",
"introDetail": "通過此選項,您仍然運行您自己的 Pangolin 節點 - - 您的隧道、TLS 終止,並且流量在您的伺服器上保持所有狀態。 不同之處在於,管理和監測是通過我們的雲層儀錶板進行的,該儀錶板開啟了一些好處:",
"benefitSimplerOperations": {
"title": "簡單的操作",
"description": "無需運行您自己的郵件伺服器或設置複雜的警報。您將從方框中獲得健康檢查和下限提醒。"

View File

@@ -1,17 +1,42 @@
import type { NextConfig } from "next";
import createNextIntlPlugin from "next-intl/plugin";
import fs from "fs";
import path from "path";
const withNextIntl = createNextIntlPlugin();
// read allowedDevOrigins.json if it exists
let allowedDevOrigins: string[] = [];
const allowedDevOriginsPath = path.join(
process.cwd(),
"allowedDevOrigins.json"
);
if (fs.existsSync(allowedDevOriginsPath)) {
try {
const data = fs.readFileSync(allowedDevOriginsPath, "utf-8");
allowedDevOrigins = JSON.parse(data);
} catch {}
}
const nextConfig: NextConfig = {
reactStrictMode: false,
eslint: {
ignoreDuringBuilds: true
},
experimental: {
reactCompiler: true
},
output: "standalone"
reactCompiler: true,
transpilePackages: ["@novnc/novnc"],
output: "standalone",
allowedDevOrigins,
async redirects() {
return [
{
source: "/:orgId/settings/resources/proxy/:path*",
destination: "/:orgId/settings/resources/public/:path*",
permanent: true
},
{
source: "/:orgId/settings/resources/client/:path*",
destination: "/:orgId/settings/resources/private/:path*",
permanent: true
}
];
}
};
export default withNextIntl(nextConfig);

5896
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 588 KiB

After

Width:  |  Height:  |  Size: 621 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 569 KiB

After

Width:  |  Height:  |  Size: 532 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 588 KiB

After

Width:  |  Height:  |  Size: 621 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 MiB

After

Width:  |  Height:  |  Size: 574 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 434 KiB

After

Width:  |  Height:  |  Size: 410 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 274 KiB

After

Width:  |  Height:  |  Size: 516 KiB

View File

@@ -5,6 +5,7 @@ import { and, eq, inArray } from "drizzle-orm";
import createHttpError from "http-errors";
import HttpCode from "@server/types/HttpCode";
import { getUserOrgRoleIds } from "@server/lib/userOrgRoles";
import logger from "@server/logger";
export enum ActionsEnum {
createOrgUser = "createOrgUser",
@@ -20,6 +21,7 @@ export enum ActionsEnum {
getSite = "getSite",
listSites = "listSites",
updateSite = "updateSite",
restartSite = "restartSite",
resetSiteBandwidth = "resetSiteBandwidth",
reGenerateSecret = "reGenerateSecret",
createResource = "createResource",
@@ -122,8 +124,6 @@ export enum ActionsEnum {
createOrgDomain = "createOrgDomain",
deleteOrgDomain = "deleteOrgDomain",
restartOrgDomain = "restartOrgDomain",
sendUsageNotification = "sendUsageNotification",
sendTrialNotification = "sendTrialNotification",
createRemoteExitNode = "createRemoteExitNode",
updateRemoteExitNode = "updateRemoteExitNode",
getRemoteExitNode = "getRemoteExitNode",
@@ -150,14 +150,37 @@ export enum ActionsEnum {
updateAlertRule = "updateAlertRule",
deleteAlertRule = "deleteAlertRule",
listAlertRules = "listAlertRules",
listOrgLabels = "listOrgLabels",
createOrgLabel = "createOrgLabel",
updateOrgLabel = "updateOrgLabel",
deleteOrgLabel = "deleteOrgLabel",
attachLabelToItem = "attachLabelToItem",
detachLabelFromItem = "detachLabelFromItem",
getAlertRule = "getAlertRule",
createHealthCheck = "createHealthCheck",
updateHealthCheck = "updateHealthCheck",
deleteHealthCheck = "deleteHealthCheck",
listHealthChecks = "listHealthChecks",
triggerSiteAlert = "triggerSiteAlert",
triggerResourceAlert = "triggerResourceAlert",
triggerHealthCheckAlert = "triggerHealthCheckAlert"
createBrowserGatewayTarget = "createBrowserGatewayTarget",
updateBrowserGatewayTarget = "updateBrowserGatewayTarget",
deleteBrowserGatewayTarget = "deleteBrowserGatewayTarget",
getBrowserGatewayTarget = "getBrowserGatewayTarget",
listBrowserGatewayTargets = "listBrowserGatewayTargets",
listResourcePolicies = "listResourcePolicies",
getResourcePolicy = "getResourcePolicy",
createResourcePolicy = "createResourcePolicy",
updateResourcePolicy = "updateResourcePolicy",
deleteResourcePolicy = "deleteResourcePolicy",
listResourcePolicyRoles = "listResourcePolicyRoles",
setResourcePolicyRoles = "setResourcePolicyRoles",
listResourcePolicyUsers = "listResourcePolicyUsers",
setResourcePolicyUsers = "setResourcePolicyUsers",
setResourcePolicyPassword = "setResourcePolicyPassword",
setResourcePolicyPincode = "setResourcePolicyPincode",
setResourcePolicyHeaderAuth = "setResourcePolicyHeaderAuth",
setResourcePolicyWhitelist = "setResourcePolicyWhitelist",
setResourcePolicyRules = "setResourcePolicyRules",
createOrgWideLauncherView = "createOrgWideLauncherView"
}
export async function checkUserActionPermission(
@@ -190,6 +213,23 @@ export async function checkUserActionPermission(
}
}
// If no direct permission, check role-based permission (any of user's roles)
const roleActionPermission = await db
.select()
.from(roleActions)
.where(
and(
eq(roleActions.actionId, actionId),
inArray(roleActions.roleId, userOrgRoleIds),
eq(roleActions.orgId, req.userOrgId!)
)
)
.limit(1);
if (roleActionPermission.length > 0) {
return true;
}
// Check if the user has direct permission for the action in the current org
const userActionPermission = await db
.select()
@@ -207,20 +247,7 @@ export async function checkUserActionPermission(
return true;
}
// If no direct permission, check role-based permission (any of user's roles)
const roleActionPermission = await db
.select()
.from(roleActions)
.where(
and(
eq(roleActions.actionId, actionId),
inArray(roleActions.roleId, userOrgRoleIds),
eq(roleActions.orgId, req.userOrgId!)
)
)
.limit(1);
return roleActionPermission.length > 0;
return false;
} catch (error) {
console.error("Error checking user action permission:", error);
throw createHttpError(

View File

@@ -1,6 +1,12 @@
import { db } from "@server/db";
import { and, eq, inArray } from "drizzle-orm";
import { roleResources, userResources } from "@server/db";
import { and, eq, inArray, isNull, or } from "drizzle-orm";
import {
rolePolicies,
roleResources,
resources,
userPolicies,
userResources
} from "@server/db";
export async function canUserAccessResource({
userId,
@@ -11,9 +17,14 @@ export async function canUserAccessResource({
resourceId: number;
roleIds: number[];
}): Promise<boolean> {
const roleResourceAccess =
const [
roleResourceAccess,
rolePolicyAccess,
userResourceAccess,
userPolicyAccess
] = await Promise.all([
roleIds.length > 0
? await db
? db
.select()
.from(roleResources)
.where(
@@ -23,26 +34,87 @@ export async function canUserAccessResource({
)
)
.limit(1)
: [];
if (roleResourceAccess.length > 0) {
return true;
}
const userResourceAccess = await db
.select()
.from(userResources)
.where(
and(
eq(userResources.userId, userId),
eq(userResources.resourceId, resourceId)
: [],
roleIds.length > 0
? db
.select({
roleId: rolePolicies.roleId,
resourcePolicyId: rolePolicies.resourcePolicyId
})
.from(rolePolicies)
.innerJoin(
resources,
// Shared policy wins; only use default policy when no shared
// policy is assigned to the resource.
or(
eq(
resources.resourcePolicyId,
rolePolicies.resourcePolicyId
),
and(
isNull(resources.resourcePolicyId),
eq(
resources.defaultResourcePolicyId,
rolePolicies.resourcePolicyId
)
)
)
)
.where(
and(
eq(resources.resourceId, resourceId),
inArray(rolePolicies.roleId, roleIds)
)
)
.limit(1)
: [],
db
.select()
.from(userResources)
.where(
and(
eq(userResources.userId, userId),
eq(userResources.resourceId, resourceId)
)
)
)
.limit(1);
.limit(1),
db
.select({
userId: userPolicies.userId,
resourcePolicyId: userPolicies.resourcePolicyId
})
.from(userPolicies)
.innerJoin(
resources,
// Shared policy wins; only use default policy when no shared
// policy is assigned to the resource.
or(
eq(
resources.resourcePolicyId,
userPolicies.resourcePolicyId
),
and(
isNull(resources.resourcePolicyId),
eq(
resources.defaultResourcePolicyId,
userPolicies.resourcePolicyId
)
)
)
)
.where(
and(
eq(resources.resourceId, resourceId),
eq(userPolicies.userId, userId)
)
)
.limit(1)
]);
if (userResourceAccess.length > 0) {
return true;
}
return false;
return (
roleResourceAccess.length > 0 ||
rolePolicyAccess.length > 0 ||
userResourceAccess.length > 0 ||
userPolicyAccess.length > 0
);
}

View File

@@ -12,7 +12,7 @@ import {
users
} from "@server/db";
import { db } from "@server/db";
import { eq, inArray } from "drizzle-orm";
import { and, eq, inArray, ne } from "drizzle-orm";
import config from "@server/lib/config";
import type { RandomReader } from "@oslojs/crypto/random";
import { generateRandomString } from "@oslojs/crypto/random";
@@ -136,6 +136,45 @@ export async function invalidateAllSessions(userId: string): Promise<void> {
}
}
export async function invalidateAllSessionsExceptCurrent(
userId: string,
currentSessionId: string
): Promise<void> {
try {
await db.transaction(async (trx) => {
const userSessions = await trx
.select()
.from(sessions)
.where(
and(
eq(sessions.userId, userId),
ne(sessions.sessionId, currentSessionId)
)
);
if (userSessions.length > 0) {
await trx.delete(resourceSessions).where(
inArray(
resourceSessions.userSessionId,
userSessions.map((s) => s.sessionId)
)
);
}
await trx
.delete(sessions)
.where(
and(
eq(sessions.userId, userId),
ne(sessions.sessionId, currentSessionId)
)
);
});
} catch (e) {
logger.error("Failed to invalidate user sessions except current", e);
}
}
export function serializeSessionCookie(
token: string,
isSecure: boolean,

View File

@@ -19,6 +19,9 @@ export async function createResourceSession(opts: {
userSessionId?: string | null;
whitelistId?: number | null;
accessTokenId?: string | null;
policyPasswordId?: number | null;
policyPincodeId?: number | null;
policyWhitelistId?: number | null;
doNotExtend?: boolean;
expiresAt?: number | null;
sessionLength?: number | null;
@@ -28,7 +31,10 @@ export async function createResourceSession(opts: {
!opts.pincodeId &&
!opts.whitelistId &&
!opts.accessTokenId &&
!opts.userSessionId
!opts.userSessionId &&
!opts.policyPasswordId &&
!opts.policyPincodeId &&
!opts.policyWhitelistId
) {
throw new Error("Auth method must be provided");
}
@@ -49,6 +55,9 @@ export async function createResourceSession(opts: {
whitelistId: opts.whitelistId || null,
doNotExtend: opts.doNotExtend || false,
accessTokenId: opts.accessTokenId || null,
policyPasswordId: opts.policyPasswordId || null,
policyPincodeId: opts.policyPincodeId || null,
policyWhitelistId: opts.policyWhitelistId || null,
isRequestToken: opts.isRequestToken || false,
userSessionId: opts.userSessionId || null,
issuedAt: new Date().getTime()

View File

@@ -795,10 +795,13 @@ export const COUNTRIES = [
name: "Serbia",
code: "RS"
},
{
name: "Serbia and Montenegro",
code: "CS"
},
// Removed as this is a deprecated ISO country code, not supported anymore
// Also the individual flags for Serbia & Montenegro are already included in the list
// more details: https://en.wikipedia.org/wiki/ISO_3166-2:CS
// {
// name: "Serbia and Montenegro",
// code: "CS"
// },
{
name: "Seychelles",
code: "SC"

View File

@@ -1,94 +1,53 @@
{
"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",
"Mac14,10": "MacBook Pro",
"Mac14,12": "Mac mini",
"Mac14,13": "Mac Studio",
"Mac14,14": "Mac Studio",
"Mac14,15": "MacBook Air",
"Mac14,2": "MacBook Air",
"Mac14,3": "Mac mini",
"Mac14,5": "MacBook Pro",
"Mac14,6": "MacBook Pro",
"Mac14,7": "MacBook Pro",
"Mac14,8": "Mac Pro",
"Mac14,9": "MacBook Pro",
"Mac15,10": "MacBook Pro",
"Mac15,11": "MacBook Pro",
"Mac15,12": "MacBook Air",
"Mac15,13": "MacBook Air",
"Mac15,14": "Mac Studio",
"Mac15,3": "MacBook Pro",
"Mac15,4": "iMac",
"Mac15,5": "iMac",
"Mac15,6": "MacBook Pro",
"Mac15,7": "MacBook Pro",
"Mac15,8": "MacBook Pro",
"Mac15,9": "MacBook Pro",
"Mac16,1": "MacBook Pro",
"Mac16,10": "Mac mini",
"Mac16,11": "Mac mini",
"Mac16,12": "MacBook Air",
"Mac16,13": "MacBook Air",
"Mac16,2": "iMac",
"Mac16,3": "iMac",
"Mac16,5": "MacBook Pro",
"Mac16,6": "MacBook Pro",
"Mac16,7": "MacBook Pro",
"Mac16,8": "MacBook Pro",
"Mac16,9": "Mac Studio",
"Mac17,2": "MacBook Pro",
"Mac17,3": "MacBook Air",
"Mac17,4": "MacBook Air",
"Mac17,5": "MacBook Neo",
"Mac17,6": "MacBook Pro",
"Mac17,7": "MacBook Pro",
"Mac17,8": "MacBook Pro",
"Mac17,9": "MacBook Pro",
"MacBook1,1": "MacBook",
"MacBook10,1": "MacBook",
"MacBook2,1": "MacBook",
"MacBook3,1": "MacBook",
"MacBook4,1": "MacBook",
@@ -98,8 +57,8 @@
"MacBook7,1": "MacBook",
"MacBook8,1": "MacBook",
"MacBook9,1": "MacBook",
"MacBook10,1": "MacBook",
"MacBookAir1,1": "MacBook Air",
"MacBookAir10,1": "MacBook Air",
"MacBookAir2,1": "MacBook Air",
"MacBookAir3,1": "MacBook Air",
"MacBookAir3,2": "MacBook Air",
@@ -114,88 +73,163 @@
"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",
"MacBookPro12,1": "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,2": "MacBook Pro",
"MacBookPro15,3": "MacBook Pro",
"MacBookPro15,4": "MacBook Pro",
"MacBookPro16,1": "MacBook Pro",
"MacBookPro16,3": "MacBook Pro",
"MacBookPro16,2": "MacBook Pro",
"MacBookPro16,3": "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",
"MacBookPro18,3": "MacBook Pro",
"MacBookPro18,4": "MacBook Pro",
"MacBookPro2,1": "MacBook Pro",
"MacBookPro2,2": "MacBook Pro",
"MacBookPro3,1": "MacBook Pro",
"MacBookPro4,1": "MacBook Pro",
"MacBookPro5,1": "MacBook Pro",
"MacBookPro5,2": "MacBook Pro",
"MacBookPro5,3": "MacBook Pro",
"MacBookPro5,4": "MacBook Pro",
"MacBookPro5,5": "MacBook Pro",
"MacBookPro6,1": "MacBook Pro",
"MacBookPro6,2": "MacBook Pro",
"MacBookPro7,1": "MacBook Pro",
"MacBookPro8,1": "MacBook Pro",
"MacBookPro8,2": "MacBook Pro",
"MacBookPro8,3": "MacBook Pro",
"MacBookPro9,1": "MacBook Pro",
"MacBookPro9,2": "MacBook Pro",
"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",
"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",
"Macmini9,1": "Mac mini",
"PowerBook1,1": "PowerBook",
"PowerBook2,1": "iBook",
"PowerBook2,2": "iBook",
"PowerBook3,1": "PowerBook",
"PowerBook3,2": "PowerBook",
"PowerBook3,3": "PowerBook",
"PowerBook3,4": "PowerBook",
"PowerBook3,5": "PowerBook",
"PowerBook6,1": "PowerBook",
"PowerBook4,1": "iBook",
"PowerBook4,2": "iBook",
"PowerBook4,3": "iBook",
"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",
"PowerBook6,1": "PowerBook",
"PowerBook6,2": "PowerBook",
"PowerBook6,3": "iBook",
"PowerBook6,4": "PowerBook",
"PowerBook6,5": "iBook",
"PowerBook6,7": "iBook",
"PowerBook6,8": "PowerBook",
"PowerMac1,1": "Power Macintosh",
"PowerMac1,2": "Power Macintosh",
"PowerMac10,1": "Mac mini",
"PowerMac10,2": "Mac mini",
"PowerMac11,2": "Power Macintosh",
"PowerMac12,1": "iMac",
"PowerMac2,1": "iMac",
"PowerMac2,2": "iMac",
"PowerMac3,1": "Mac Server",
"PowerMac3,3": "Power Macintosh",
"PowerMac3,4": "Power Macintosh",
"PowerMac3,5": "Power Macintosh",
"PowerMac3,6": "Power Macintosh",
"PowerMac4,1": "iMac",
"PowerMac4,2": "iMac",
"PowerMac4,4": "eMac",
"PowerMac4,5": "iMac",
"PowerMac5,1": "Power Macintosh",
"PowerMac6,1": "iMac",
"PowerMac6,3": "iMac",
"PowerMac6,4": "eMac",
"PowerMac7,2": "Power Macintosh",
"PowerMac7,3": "Power Macintosh",
"PowerMac8,1": "iMac",
"PowerMac8,2": "iMac",
"PowerMac9,1": "Power Macintosh",
"RackMac1,1": "Xserve",
"RackMac1,2": "Xserve",
"RackMac3,1": "Xserve",
"Xserve1,1": "Xserve",
"Xserve2,1": "Xserve",
"Xserve3,1": "Xserve"
}
"Xserve3,1": "Xserve",
"iMac,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,2": "iMac",
"iMac14,3": "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,1": "iMac",
"iMac19,2": "iMac",
"iMac20,1": "iMac",
"iMac20,2": "iMac",
"iMac21,1": "iMac",
"iMac21,2": "iMac",
"iMac4,1": "iMac",
"iMac4,2": "iMac",
"iMac5,1": "iMac",
"iMac5,2": "iMac",
"iMac6,1": "iMac",
"iMac7,1": "iMac",
"iMac8,1": "iMac",
"iMac9,1": "iMac",
"iMacPro1,1": "iMac Pro"
}

View File

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

View File

@@ -87,7 +87,7 @@ function createDb() {
export const db = createDb();
export default db;
export const primaryDb = db.$primary;
export const primaryDb = db.$primary as typeof db; // is this typeof a problem - technically they are different types
export type Transaction = Parameters<
Parameters<(typeof db)["transaction"]>[0]
>[0];

View File

@@ -4,3 +4,4 @@ export * from "./safeRead";
export * from "./schema/schema";
export * from "./schema/privateSchema";
export * from "./migrate";
export { alias } from "drizzle-orm/pg-core";

View File

@@ -2,7 +2,7 @@ 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 { db as mainDb } from "./driver";
import { createPool } from "./poolConfig";
function createLogsDb() {
@@ -63,8 +63,7 @@ function createLogsDb() {
})
);
} else {
const maxReplicaConnections =
poolConfig?.max_replica_connections || 20;
const maxReplicaConnections = poolConfig?.max_replica_connections || 20;
for (const conn of replicaConnections) {
const replicaPool = createPool(
conn.connection_string,
@@ -91,4 +90,4 @@ function createLogsDb() {
export const logsDb = createLogsDb();
export default logsDb;
export const primaryLogsDb = logsDb.$primary;
export const primaryLogsDb = logsDb.$primary;

View File

@@ -1,5 +1,4 @@
import { Pool, PoolConfig } from "pg";
import logger from "@server/logger";
export function createPoolConfig(
connectionString: string,
@@ -27,7 +26,7 @@ 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(
console.error(
`Unexpected error on idle ${label} database client: ${err.message}`
);
});
@@ -36,7 +35,7 @@ export function attachPoolErrorHandlers(pool: Pool, label: string): void {
// Set a statement timeout on every new connection so a single slow
// query can't block the pool forever
client.query("SET statement_timeout = '30s'").catch((err: Error) => {
logger.warn(
console.warn(
`Failed to set statement_timeout on ${label} client: ${err.message}`
);
});
@@ -60,4 +59,4 @@ export function createPool(
);
attachPoolErrorHandlers(pool, label);
return pool;
}
}

View File

@@ -2,6 +2,7 @@ import {
pgTable,
serial,
varchar,
unique,
boolean,
integer,
bigint,
@@ -11,7 +12,7 @@ import {
primaryKey,
uniqueIndex
} from "drizzle-orm/pg-core";
import { InferSelectModel } from "drizzle-orm";
import { InferSelectModel, sql } from "drizzle-orm";
import {
domains,
orgs,
@@ -19,12 +20,13 @@ import {
roles,
users,
exitNodes,
sessions,
clients,
resources,
siteResources,
targetHealthCheck,
sites
sites,
clients,
sessions,
labels
} from "./schema";
export const certificates = pgTable("certificates", {
@@ -197,6 +199,42 @@ export const remoteExitNodes = pgTable("remoteExitNode", {
})
});
export const remoteExitNodeResources = pgTable("remoteExitNodeResources", {
remoteExitNodeResourceId: serial("remoteExitNodeResourceId").primaryKey(),
remoteExitNodeId: varchar("remoteExitNodeId")
.notNull()
.references(() => remoteExitNodes.remoteExitNodeId, {
onDelete: "cascade"
}),
destination: varchar("destination").notNull() // a cidr range
});
export const remoteExitNodePreferenceLabels = pgTable(
// this controls what sites are enforced to connect to this node
"remoteExitNodePreferenceLabels",
{
remoteExitNodePreferenceLabelId: serial(
"remoteExitNodePreferenceLabelId"
).primaryKey(),
remoteExitNodeId: varchar("remoteExitNodeId")
.references(() => remoteExitNodes.remoteExitNodeId, {
onDelete: "cascade"
})
.notNull(),
labelId: integer("labelId")
.references(() => labels.labelId, {
onDelete: "cascade"
})
.notNull()
},
(t) => [
unique("remote_exit_node_preference_label_uniq").on(
t.remoteExitNodeId,
t.labelId
)
]
);
export const remoteExitNodeSessions = pgTable("remoteExitNodeSession", {
sessionId: varchar("id").primaryKey(),
remoteExitNodeId: varchar("remoteExitNodeId")
@@ -207,17 +245,28 @@ export const remoteExitNodeSessions = pgTable("remoteExitNodeSession", {
expiresAt: bigint("expiresAt", { mode: "number" }).notNull()
});
export const loginPage = pgTable("loginPage", {
loginPageId: serial("loginPageId").primaryKey(),
subdomain: varchar("subdomain"),
fullDomain: varchar("fullDomain"),
exitNodeId: integer("exitNodeId").references(() => exitNodes.exitNodeId, {
onDelete: "set null"
}),
domainId: varchar("domainId").references(() => domains.domainId, {
onDelete: "set null"
})
});
export const loginPage = pgTable(
"loginPage",
{
loginPageId: serial("loginPageId").primaryKey(),
subdomain: varchar("subdomain"),
fullDomain: varchar("fullDomain"),
exitNodeId: integer("exitNodeId").references(
() => exitNodes.exitNodeId,
{
onDelete: "set null"
}
),
domainId: varchar("domainId").references(() => domains.domainId, {
onDelete: "set null"
})
},
(t) => [
index("idx_loginpage_fulldomain")
.on(t.fullDomain)
.where(sql`${t.fullDomain} IS NOT NULL`)
]
);
export const loginPageOrg = pgTable("loginPageOrg", {
loginPageId: integer("loginPageId")
@@ -332,6 +381,7 @@ export const connectionAuditLog = pgTable(
clientId: integer("clientId").references(() => clients.clientId, {
onDelete: "cascade"
}),
clientEndpoint: text("clientEndpoint"),
userId: text("userId").references(() => users.userId, {
onDelete: "cascade"
}),
@@ -439,6 +489,8 @@ export const eventStreamingDestinations = pgTable(
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),
lastError: text("lastError"), // last send error message, null if healthy
lastErrorAt: bigint("lastErrorAt", { mode: "number" }), // epoch ms of last error, null if healthy
createdAt: bigint("createdAt", { mode: "number" }).notNull(),
updatedAt: bigint("updatedAt", { mode: "number" }).notNull()
}
@@ -484,6 +536,7 @@ export const alertRules = pgTable("alertRules", {
| "health_check_toggle"
| "resource_healthy"
| "resource_unhealthy"
| "resource_degraded"
| "resource_toggle"
>()
.notNull(),
@@ -565,6 +618,17 @@ export const alertWebhookActions = pgTable("alertWebhookActions", {
lastSentAt: bigint("lastSentAt", { mode: "number" }) // nullable
});
export const trialNotifications = pgTable("trialNotifications", {
notificationId: serial("notificationId").primaryKey(),
subscriptionId: varchar("subscriptionId", { length: 255 })
.notNull()
.references(() => subscriptions.subscriptionId, {
onDelete: "cascade"
}),
notificationType: varchar("notificationType", { length: 50 }).notNull(), // trial_ending_5d, trial_ending_24h, trial_ended
sentAt: bigint("sentAt", { mode: "number" }).notNull()
});
export type Approval = InferSelectModel<typeof approvals>;
export type Limit = InferSelectModel<typeof limits>;
export type Account = InferSelectModel<typeof account>;
@@ -603,3 +667,12 @@ export type EventStreamingCursor = InferSelectModel<
typeof eventStreamingCursors
>;
export type AlertResources = InferSelectModel<typeof alertResources>;
export type AlertHealthChecks = InferSelectModel<typeof alertHealthChecks>;
export type AlertSites = InferSelectModel<typeof alertSites>;
export type AlertRules = InferSelectModel<typeof alertRules>;
export type AlertEmailActions = InferSelectModel<typeof alertEmailActions>;
export type AlertEmailRecipients = InferSelectModel<
typeof alertEmailRecipients
>;
export type AlertWebhookActions = InferSelectModel<typeof alertWebhookActions>;
export type TrialNotification = InferSelectModel<typeof trialNotifications>;

File diff suppressed because it is too large Load Diff

View File

@@ -17,22 +17,37 @@ import {
resourceHeaderAuth,
ResourceHeaderAuth,
resourceRules,
resourcePolicyRules,
resources,
roleResources,
rolePolicies,
sessions,
userResources,
userPolicies,
users,
ResourceHeaderAuthExtendedCompatibility,
resourceHeaderAuthExtendedCompatibility
resourceHeaderAuthExtendedCompatibility,
resourcePolicies,
resourcePolicyPincode,
ResourcePolicyPincode,
resourcePolicyPassword,
ResourcePolicyPassword,
resourcePolicyHeaderAuth,
ResourcePolicyHeaderAuth
} from "@server/db";
import { and, eq, inArray } from "drizzle-orm";
import { alias } from "@server/db";
import { and, eq, inArray, isNull, or, sql } from "drizzle-orm";
import logger from "@server/logger";
export type ResourceWithAuth = {
resource: Resource | null;
pincode: ResourcePincode | null;
password: ResourcePassword | null;
headerAuth: ResourceHeaderAuth | null;
pincode: ResourcePincode | ResourcePolicyPincode | null;
password: ResourcePassword | ResourcePolicyPassword | null;
headerAuth: ResourceHeaderAuth | ResourcePolicyHeaderAuth | null;
headerAuthExtendedCompatibility: ResourceHeaderAuthExtendedCompatibility | null;
applyRules: boolean | null;
sso: boolean | null;
emailWhitelistEnabled: boolean | null;
org: Org;
};
@@ -47,7 +62,44 @@ export type UserSessionWithUser = {
export async function getResourceByDomain(
domain: string
): Promise<ResourceWithAuth | null> {
const [result] = await db
// Build wildcard domain variants to match against.
// For a domain like "me.example.test.com", we want to match:
// - "*.example.test.com" (subdomain wildcard)
// - "*.test.com" (parent wildcard, i.e. just "*" subdomain on parent)
const parts = domain.split(".");
const wildcardCandidates: string[] = [];
for (let i = 1; i < parts.length; i++) {
wildcardCandidates.push(`*.${parts.slice(i).join(".")}`);
}
const sharedPolicy = alias(resourcePolicies, "sharedPolicy");
const defaultPolicy = alias(resourcePolicies, "defaultPolicy");
const sharedPolicyPincode = alias(
resourcePolicyPincode,
"sharedPolicyPincode"
);
const defaultPolicyPincode = alias(
resourcePolicyPincode,
"defaultPolicyPincode"
);
const sharedPolicyPassword = alias(
resourcePolicyPassword,
"sharedPolicyPassword"
);
const defaultPolicyPassword = alias(
resourcePolicyPassword,
"defaultPolicyPassword"
);
const sharedPolicyHeaderAuth = alias(
resourcePolicyHeaderAuth,
"sharedPolicyHeaderAuth"
);
const defaultPolicyHeaderAuth = alias(
resourcePolicyHeaderAuth,
"defaultPolicyHeaderAuth"
);
const potentialResults = await db
.select()
.from(resources)
.leftJoin(
@@ -69,21 +121,133 @@ export async function getResourceByDomain(
resources.resourceId
)
)
.leftJoin(
sharedPolicy,
eq(sharedPolicy.resourcePolicyId, resources.resourcePolicyId)
)
.leftJoin(
sharedPolicyPincode,
eq(
sharedPolicyPincode.resourcePolicyId,
sharedPolicy.resourcePolicyId
)
)
.leftJoin(
sharedPolicyPassword,
eq(
sharedPolicyPassword.resourcePolicyId,
sharedPolicy.resourcePolicyId
)
)
.leftJoin(
sharedPolicyHeaderAuth,
eq(
sharedPolicyHeaderAuth.resourcePolicyId,
sharedPolicy.resourcePolicyId
)
)
.leftJoin(
defaultPolicy,
eq(
defaultPolicy.resourcePolicyId,
resources.defaultResourcePolicyId
)
)
.leftJoin(
defaultPolicyPincode,
eq(
defaultPolicyPincode.resourcePolicyId,
defaultPolicy.resourcePolicyId
)
)
.leftJoin(
defaultPolicyPassword,
eq(
defaultPolicyPassword.resourcePolicyId,
defaultPolicy.resourcePolicyId
)
)
.leftJoin(
defaultPolicyHeaderAuth,
eq(
defaultPolicyHeaderAuth.resourcePolicyId,
defaultPolicy.resourcePolicyId
)
)
.innerJoin(orgs, eq(orgs.orgId, resources.orgId))
.where(eq(resources.fullDomain, domain))
.limit(1);
.where(
or(
// Exact match
eq(resources.fullDomain, domain),
// Wildcard match: resource fullDomain is one of the wildcard candidates
wildcardCandidates.length > 0
? and(
eq(resources.wildcard, true),
inArray(resources.fullDomain, wildcardCandidates)
)
: sql`false`
)
);
if (!potentialResults.length) {
return null;
}
// Prefer exact match over wildcard match
const exactMatch = potentialResults.find(
(r) => r.resources?.fullDomain === domain
);
const result = exactMatch ?? potentialResults[0];
if (!result) {
return null;
}
// If a shared (custom) policy is assigned to the resource, use ONLY
// its values — do not fall back to the default policy. The default
// policy is only consulted when no shared policy is assigned at all.
const hasSharedPolicy = result.sharedPolicy !== null;
const effectivePolicyPincode = hasSharedPolicy
? result.sharedPolicyPincode
: (result.defaultPolicyPincode ?? null);
const effectivePolicyPassword = hasSharedPolicy
? result.sharedPolicyPassword
: (result.defaultPolicyPassword ?? null);
const effectivePolicyHeaderAuth = hasSharedPolicy
? result.sharedPolicyHeaderAuth
: (result.defaultPolicyHeaderAuth ?? null);
const selectedPolicy = hasSharedPolicy
? result.sharedPolicy
: result.defaultPolicy;
const effectiveApplyRules =
selectedPolicy?.applyRules ?? result.resources.applyRules;
const effectiveSSO = selectedPolicy?.sso ?? result.resources.sso;
const effectiveEmailWhitelistEnabled =
selectedPolicy?.emailWhitelistEnabled ??
result.resources.emailWhitelistEnabled;
return {
resource: result.resources,
pincode: result.resourcePincode,
password: result.resourcePassword,
headerAuth: result.resourceHeaderAuth,
headerAuthExtendedCompatibility:
result.resourceHeaderAuthExtendedCompatibility,
resource: {
...result.resources,
applyRules: effectiveApplyRules,
sso: effectiveSSO,
emailWhitelistEnabled: effectiveEmailWhitelistEnabled
}, // doing this for backward compatability so the remote nodes get the value as part of the resource struct
pincode: effectivePolicyPincode ?? result.resourcePincode,
password: effectivePolicyPassword ?? result.resourcePassword,
headerAuth: effectivePolicyHeaderAuth ?? result.resourceHeaderAuth,
headerAuthExtendedCompatibility: effectivePolicyHeaderAuth
? ({
headerAuthExtendedCompatibilityId: 0,
resourceId: result.resources.resourceId,
extendedCompatibilityIsActivated:
effectivePolicyHeaderAuth.extendedCompatibility
} as ResourceHeaderAuthExtendedCompatibility)
: result.resourceHeaderAuthExtendedCompatibility,
applyRules: effectiveApplyRules,
sso: effectiveSSO,
emailWhitelistEnabled: effectiveEmailWhitelistEnabled,
org: result.orgs
};
}
@@ -123,58 +287,165 @@ export async function getRoleName(roleId: number): Promise<string | null> {
}
/**
* Check if role has access to resource
* Check if role has access to resource (direct or via resource policy)
*/
export async function getRoleResourceAccess(
resourceId: number,
roleIds: number[]
) {
const roleResourceAccess = await db
.select()
.from(roleResources)
.where(
and(
eq(roleResources.resourceId, resourceId),
inArray(roleResources.roleId, roleIds)
const [direct, viaPolicies] = await Promise.all([
db
.select()
.from(roleResources)
.where(
and(
eq(roleResources.resourceId, resourceId),
inArray(roleResources.roleId, roleIds)
)
),
db
.select({
roleId: rolePolicies.roleId,
resourcePolicyId: rolePolicies.resourcePolicyId
})
.from(rolePolicies)
.innerJoin(
resources,
// Shared policy wins; only use default policy when no shared
// policy is assigned to the resource.
or(
eq(
resources.resourcePolicyId,
rolePolicies.resourcePolicyId
),
and(
isNull(resources.resourcePolicyId),
eq(
resources.defaultResourcePolicyId,
rolePolicies.resourcePolicyId
)
)
)
)
);
.where(
and(
eq(resources.resourceId, resourceId),
inArray(rolePolicies.roleId, roleIds)
)
)
]);
return roleResourceAccess.length > 0 ? roleResourceAccess : null;
const combined = [...direct, ...viaPolicies];
return combined.length > 0 ? combined : null;
}
/**
* Check if user has direct access to resource
* Check if user has access to resource (direct or via resource policy)
*/
export async function getUserResourceAccess(
userId: string,
resourceId: number
) {
const userResourceAccess = await db
.select()
.from(userResources)
.where(
and(
eq(userResources.userId, userId),
eq(userResources.resourceId, resourceId)
const [direct, viaPolicies] = await Promise.all([
db
.select()
.from(userResources)
.where(
and(
eq(userResources.userId, userId),
eq(userResources.resourceId, resourceId)
)
)
)
.limit(1);
.limit(1),
db
.select({
userId: userPolicies.userId,
resourcePolicyId: userPolicies.resourcePolicyId
})
.from(userPolicies)
.innerJoin(
resources,
// Shared policy wins; only use default policy when no shared
// policy is assigned to the resource.
or(
eq(
resources.resourcePolicyId,
userPolicies.resourcePolicyId
),
and(
isNull(resources.resourcePolicyId),
eq(
resources.defaultResourcePolicyId,
userPolicies.resourcePolicyId
)
)
)
)
.where(
and(
eq(resources.resourceId, resourceId),
eq(userPolicies.userId, userId)
)
)
.limit(1)
]);
return userResourceAccess.length > 0 ? userResourceAccess[0] : null;
return direct[0] ?? viaPolicies[0] ?? null;
}
/**
* Get resource rules for a given resource
* Get resource rules for a given resource (direct and via resource policy)
*/
export async function getResourceRules(
resourceId: number
): Promise<ResourceRule[]> {
const rules = await db
.select()
.from(resourceRules)
.where(eq(resourceRules.resourceId, resourceId));
const [directRules, policyRules] = await Promise.all([
db
.select()
.from(resourceRules)
.where(eq(resourceRules.resourceId, resourceId)),
db
.select({
ruleId: resourcePolicyRules.ruleId,
resourceId: sql<number>`${resourceId}`,
enabled: resourcePolicyRules.enabled,
priority: resourcePolicyRules.priority,
action: resourcePolicyRules.action,
match: resourcePolicyRules.match,
value: resourcePolicyRules.value
})
.from(resourcePolicyRules)
.innerJoin(
resources,
// Shared policy wins; only use default policy when no shared
// policy is assigned to the resource.
or(
eq(
resources.resourcePolicyId,
resourcePolicyRules.resourcePolicyId
),
and(
isNull(resources.resourcePolicyId),
eq(
resources.defaultResourcePolicyId,
resourcePolicyRules.resourcePolicyId
)
)
)
)
.where(eq(resources.resourceId, resourceId))
]);
return rules;
const maxDirectPriority = directRules.reduce(
(max, r) => Math.max(max, r.priority),
0
);
const offsetPolicyRules = policyRules.map((r) => ({
...r,
priority: maxDirectPriority + r.priority
}));
return [...directRules, ...offsetPolicyRules] as ResourceRule[];
}
/**

View File

@@ -13,6 +13,30 @@ bootstrapVolume();
function createDb() {
const sqlite = new Database(location);
if (process.env.ENABLE_SQLITE_WAL_MODE == "true") {
// Enable WAL mode — allows concurrent readers + single writer, preventing
// contention across subsystems (verifySession, Traefik, audit, ping).
// NOTE: journal_mode persists in the DB file once set; unsetting this
// env var does NOT revert an existing WAL database.
sqlite.pragma("journal_mode = WAL");
// NORMAL sync mode: safe with WAL, reduces write lock hold time.
sqlite.pragma("synchronous = NORMAL");
}
// No busy_timeout pragma: better-sqlite3 already arms
// sqlite3_busy_timeout(db, 5000) via its default `timeout` option
// (lib/database.js), so an explicit pragma is redundant.
// Intentionally NOT setting cache_size or mmap_size: a large page cache plus
// a multi-hundred-MB mmap region inflate RSS and cause page-cache thrashing
// on small (~1 GB) instances. Leave SQLite on its conservative defaults.
// Intentionally NOT wrapping prepare()/statements: better-sqlite3 finalizes
// sqlite3_stmt in the Statement destructor at GC, and drizzle-orm prepares a
// fresh statement per query (no statement cache), so statements cannot
// accumulate. better-sqlite3 11.x exposes no Statement.finalize() at all.
return DrizzleSqlite(sqlite, {
schema
});
@@ -23,7 +47,7 @@ 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 {

View File

@@ -4,3 +4,4 @@ export * from "./safeRead";
export * from "./schema/schema";
export * from "./schema/privateSchema";
export * from "./migrate";
export { alias } from "drizzle-orm/sqlite-core";

View File

@@ -12,6 +12,7 @@ import {
clients,
domains,
exitNodes,
labels,
orgs,
resources,
roles,
@@ -192,6 +193,44 @@ export const remoteExitNodes = sqliteTable("remoteExitNode", {
})
});
export const remoteExitNodeResources = sqliteTable("remoteExitNodeResources", {
remoteExitNodeResourceId: integer("remoteExitNodeResourceId").primaryKey({
autoIncrement: true
}),
remoteExitNodeId: text("remoteExitNodeId")
.notNull()
.references(() => remoteExitNodes.remoteExitNodeId, {
onDelete: "cascade"
}),
destination: text("destination").notNull() // a cidr range
});
export const remoteExitNodePreferenceLabels = sqliteTable(
// this controls what sites are enforced to connect to this node
"remoteExitNodePreferenceLabels",
{
remoteExitNodePreferenceLabelId: integer(
"remoteExitNodePreferenceLabelId"
).primaryKey({ autoIncrement: true }),
remoteExitNodeId: text("remoteExitNodeId")
.references(() => remoteExitNodes.remoteExitNodeId, {
onDelete: "cascade"
})
.notNull(),
labelId: integer("labelId")
.references(() => labels.labelId, {
onDelete: "cascade"
})
.notNull()
},
(t) => [
uniqueIndex("remote_exit_node_preference_label_uniq").on(
t.remoteExitNodeId,
t.labelId
)
]
);
export const remoteExitNodeSessions = sqliteTable("remoteExitNodeSession", {
sessionId: text("id").primaryKey(),
remoteExitNodeId: text("remoteExitNodeId")
@@ -329,6 +368,7 @@ export const connectionAuditLog = sqliteTable(
clientId: integer("clientId").references(() => clients.clientId, {
onDelete: "cascade"
}),
clientEndpoint: text("clientEndpoint"),
userId: text("userId").references(() => users.userId, {
onDelete: "cascade"
}),
@@ -425,15 +465,25 @@ export const eventStreamingDestinations = sqliteTable(
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),
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),
lastError: text("lastError"), // last send error message, null if healthy
lastErrorAt: integer("lastErrorAt"), // epoch ms of last error, null if healthy
createdAt: integer("createdAt").notNull(),
updatedAt: integer("updatedAt").notNull()
}
@@ -476,14 +526,19 @@ export const alertRules = sqliteTable("alertRules", {
| "health_check_toggle"
| "resource_healthy"
| "resource_unhealthy"
| "resource_degraded"
| "resource_toggle"
>()
.notNull(),
enabled: integer("enabled", { mode: "boolean" }).notNull().default(true),
cooldownSeconds: integer("cooldownSeconds").notNull().default(300),
allSites: integer("allSites", { mode: "boolean" }).notNull().default(false),
allHealthChecks: integer("allHealthChecks", { mode: "boolean" }).notNull().default(false),
allResources: integer("allResources", { mode: "boolean" }).notNull().default(false),
allHealthChecks: integer("allHealthChecks", { mode: "boolean" })
.notNull()
.default(false),
allResources: integer("allResources", { mode: "boolean" })
.notNull()
.default(false),
lastTriggeredAt: integer("lastTriggeredAt"),
createdAt: integer("createdAt").notNull(),
updatedAt: integer("updatedAt").notNull()
@@ -531,23 +586,44 @@ export const alertEmailRecipients = sqliteTable("alertEmailRecipients", {
recipientId: integer("recipientId").primaryKey({ autoIncrement: true }),
emailActionId: integer("emailActionId")
.notNull()
.references(() => alertEmailActions.emailActionId, { onDelete: "cascade" }),
userId: text("userId").references(() => users.userId, { onDelete: "cascade" }),
roleId: integer("roleId").references(() => roles.roleId, { onDelete: "cascade" }),
.references(() => alertEmailActions.emailActionId, {
onDelete: "cascade"
}),
userId: text("userId").references(() => users.userId, {
onDelete: "cascade"
}),
roleId: integer("roleId").references(() => roles.roleId, {
onDelete: "cascade"
}),
email: text("email")
});
export const alertWebhookActions = sqliteTable("alertWebhookActions", {
webhookActionId: integer("webhookActionId").primaryKey({ autoIncrement: true }),
webhookActionId: integer("webhookActionId").primaryKey({
autoIncrement: true
}),
alertRuleId: integer("alertRuleId")
.notNull()
.references(() => alertRules.alertRuleId, { onDelete: "cascade" }),
webhookUrl: text("webhookUrl").notNull(),
config: text("config"), // encrypted JSON with auth config (authType, credentials)
config: text("config"), // encrypted JSON with auth config (authType, credentials)
enabled: integer("enabled", { mode: "boolean" }).notNull().default(true),
lastSentAt: integer("lastSentAt")
});
export const trialNotifications = sqliteTable("trialNotifications", {
notificationId: integer("notificationId").primaryKey({
autoIncrement: true
}),
subscriptionId: text("subscriptionId")
.notNull()
.references(() => subscriptions.subscriptionId, {
onDelete: "cascade"
}),
notificationType: text("notificationType").notNull(), // trial_ending_5d, trial_ending_24h, trial_ended
sentAt: integer("sentAt").notNull()
});
export type Approval = InferSelectModel<typeof approvals>;
export type Limit = InferSelectModel<typeof limits>;
export type Account = InferSelectModel<typeof account>;
@@ -580,3 +656,10 @@ export type EventStreamingCursor = InferSelectModel<
typeof eventStreamingCursors
>;
export type AlertResources = InferSelectModel<typeof alertResources>;
export type AlertHealthChecks = InferSelectModel<typeof alertHealthChecks>;
export type AlertSites = InferSelectModel<typeof alertSites>;
export type AlertRule = InferSelectModel<typeof alertRules>;
export type AlertEmailAction = InferSelectModel<typeof alertEmailActions>;
export type AlertEmailRecipient = InferSelectModel<typeof alertEmailRecipients>;
export type AlertWebhookAction = InferSelectModel<typeof alertWebhookActions>;
export type TrialNotification = InferSelectModel<typeof trialNotifications>;

View File

@@ -20,8 +20,10 @@ export const domains = sqliteTable("domains", {
failed: integer("failed", { mode: "boolean" }).notNull().default(false),
tries: integer("tries").notNull().default(0),
certResolver: text("certResolver"),
customCertResolver: text("customCertResolver"),
preferWildcardCert: integer("preferWildcardCert", { mode: "boolean" }),
errorMessage: text("errorMessage")
errorMessage: text("errorMessage"),
lastCheckedAt: integer("lastCheckedAt")
});
export const dnsRecords = sqliteTable("dnsRecords", {
@@ -62,7 +64,13 @@ export const orgs = sqliteTable("orgs", {
sshCaPrivateKey: text("sshCaPrivateKey"), // Encrypted SSH CA private key (PEM format)
sshCaPublicKey: text("sshCaPublicKey"), // SSH CA public key (OpenSSH format)
isBillingOrg: integer("isBillingOrg", { mode: "boolean" }),
billingOrgId: text("billingOrgId")
billingOrgId: text("billingOrgId"),
settingsEnableGlobalNewtAutoUpdate: integer(
"settingsEnableGlobalNewtAutoUpdate",
{ mode: "boolean" }
)
.notNull()
.default(false)
});
export const userDomains = sqliteTable("userDomains", {
@@ -116,11 +124,29 @@ export const sites = sqliteTable("sites", {
dockerSocketEnabled: integer("dockerSocketEnabled", { mode: "boolean" })
.notNull()
.default(true),
autoUpdateEnabled: integer("autoUpdateEnabled", { mode: "boolean" })
.notNull()
.default(false),
autoUpdateOverrideOrg: integer("autoUpdateOverrideOrg", {
mode: "boolean"
})
.notNull()
.default(false),
status: text("status").$type<"pending" | "approved">().default("approved")
});
export const resources = sqliteTable("resources", {
resourceId: integer("resourceId").primaryKey({ autoIncrement: true }),
resourcePolicyId: integer("resourcePolicyId").references(
() => resourcePolicies.resourcePolicyId,
{ onDelete: "set null" }
),
defaultResourcePolicyId: integer("defaultResourcePolicyId").references(
() => resourcePolicies.resourcePolicyId,
{
onDelete: "restrict"
}
),
resourceGuid: text("resourceGuid", { length: 36 })
.unique()
.notNull()
@@ -141,16 +167,12 @@ export const resources = sqliteTable("resources", {
blockAccess: integer("blockAccess", { mode: "boolean" })
.notNull()
.default(false),
sso: integer("sso", { mode: "boolean" }).notNull().default(true),
http: integer("http", { mode: "boolean" }).notNull().default(true),
protocol: text("protocol").notNull(),
proxyPort: integer("proxyPort"),
emailWhitelistEnabled: integer("emailWhitelistEnabled", { mode: "boolean" })
.notNull()
.default(false),
applyRules: integer("applyRules", { mode: "boolean" })
.notNull()
.default(false),
sso: integer("sso", { mode: "boolean" }),
emailWhitelistEnabled: integer("emailWhitelistEnabled", {
mode: "boolean"
}),
applyRules: integer("applyRules", { mode: "boolean" }),
enabled: integer("enabled", { mode: "boolean" }).notNull().default(true),
stickySession: integer("stickySession", { mode: "boolean" })
.notNull()
@@ -166,7 +188,6 @@ export const resources = sqliteTable("resources", {
.notNull()
.default(false),
proxyProtocolVersion: integer("proxyProtocolVersion").default(1),
maintenanceModeEnabled: integer("maintenanceModeEnabled", {
mode: "boolean"
})
@@ -178,9 +199,122 @@ export const resources = sqliteTable("resources", {
maintenanceTitle: text("maintenanceTitle"),
maintenanceMessage: text("maintenanceMessage"),
maintenanceEstimatedTime: text("maintenanceEstimatedTime"),
postAuthPath: text("postAuthPath")
postAuthPath: text("postAuthPath"),
health: text("health").default("unknown"), // "healthy", "unhealthy", "unknown"
wildcard: integer("wildcard", { mode: "boolean" }).notNull().default(false),
mode: text("mode").default("http").notNull(), // rdp, ssh, http, vnc
pamMode: text("pamMode")
.$type<"passthrough" | "push">()
.default("passthrough"),
authDaemonMode: text("authDaemonMode")
.$type<"site" | "remote" | "native">()
.default("site"),
authDaemonPort: integer("authDaemonPort").default(22123)
});
export const labels = sqliteTable("labels", {
labelId: integer("labelId").primaryKey({ autoIncrement: true }),
name: text("name").notNull(),
color: text("color").notNull(),
orgId: text("orgId")
.references(() => orgs.orgId, {
onDelete: "cascade"
})
.notNull()
});
export const launcherViews = sqliteTable("launcherViews", {
viewId: integer("viewId").primaryKey({ autoIncrement: true }),
orgId: text("orgId")
.notNull()
.references(() => orgs.orgId, { onDelete: "cascade" }),
userId: text("userId").references(() => users.userId, {
onDelete: "cascade"
}),
name: text("name").notNull(),
config: text("config").notNull(),
createdAt: text("createdAt").notNull(),
updatedAt: text("updatedAt").notNull()
});
export const siteLabels = sqliteTable(
"siteLabels",
{
siteLabelId: integer("siteLabelId").primaryKey({ autoIncrement: true }),
siteId: integer("siteId")
.references(() => sites.siteId, {
onDelete: "cascade"
})
.notNull(),
labelId: integer("labelId")
.references(() => labels.labelId, {
onDelete: "cascade"
})
.notNull()
},
(t) => [unique("site_label_uniq").on(t.siteId, t.labelId)]
);
export const resourceLabels = sqliteTable(
"resourceLabels",
{
resourceLabelId: integer("resourceLabelId").primaryKey({
autoIncrement: true
}),
resourceId: integer("resourceId")
.references(() => resources.resourceId, {
onDelete: "cascade"
})
.notNull(),
labelId: integer("labelId")
.references(() => labels.labelId, {
onDelete: "cascade"
})
.notNull()
},
(t) => [unique("resource_label_uniq").on(t.resourceId, t.labelId)]
);
export const siteResourceLabels = sqliteTable(
"siteResourceLabels",
{
siteResourceLabelId: integer("siteResourceLabelId").primaryKey({
autoIncrement: true
}),
siteResourceId: integer("siteResourceId")
.references(() => siteResources.siteResourceId, {
onDelete: "cascade"
})
.notNull(),
labelId: integer("labelId")
.references(() => labels.labelId, {
onDelete: "cascade"
})
.notNull()
},
(t) => [unique("site_resource_label_uniq").on(t.siteResourceId, t.labelId)]
);
export const clientLabels = sqliteTable(
"clientLabels",
{
clientLabelId: integer("clientLabelId").primaryKey({
autoIncrement: true
}),
clientId: integer("clientId")
.references(() => clients.clientId, {
onDelete: "cascade"
})
.notNull(),
labelId: integer("labelId")
.references(() => labels.labelId, {
onDelete: "cascade"
})
.notNull()
},
(t) => [unique("client_label_uniq").on(t.clientId, t.labelId)]
);
export const targets = sqliteTable("targets", {
targetId: integer("targetId").primaryKey({ autoIncrement: true }),
resourceId: integer("resourceId")
@@ -202,7 +336,12 @@ export const targets = sqliteTable("targets", {
pathMatchType: text("pathMatchType"), // exact, prefix, regex
rewritePath: text("rewritePath"), // if set, rewrites the path to this value before sending to the target
rewritePathType: text("rewritePathType"), // exact, prefix, regex, stripPrefix
priority: integer("priority").notNull().default(100)
priority: integer("priority").notNull().default(100),
mode: text("mode")
.$type<"http" | "tcp" | "udp" | "ssh" | "rdp" | "vnc">()
.notNull()
.default("http"),
authToken: text("authToken")
});
export const targetHealthCheck = sqliteTable("targetHealthCheck", {
@@ -217,9 +356,11 @@ export const targetHealthCheck = sqliteTable("targetHealthCheck", {
onDelete: "cascade"
})
.notNull(),
siteId: integer("siteId").references(() => sites.siteId, {
onDelete: "cascade"
}).notNull(),
siteId: integer("siteId")
.references(() => sites.siteId, {
onDelete: "cascade"
})
.notNull(),
name: text("name"),
hcEnabled: integer("hcEnabled", { mode: "boolean" })
.notNull()
@@ -279,11 +420,11 @@ export const siteResources = sqliteTable("siteResources", {
niceId: text("niceId").notNull(),
name: text("name").notNull(),
ssl: integer("ssl", { mode: "boolean" }).notNull().default(false),
mode: text("mode").$type<"host" | "cidr" | "http">().notNull(), // "host" | "cidr" | "http"
mode: text("mode").$type<"host" | "cidr" | "http" | "ssh">().notNull(), // "host" | "cidr" | "http"
scheme: text("scheme").$type<"http" | "https">(), // only for when we are doing https or http mode
proxyPort: integer("proxyPort"), // only for port mode
destinationPort: integer("destinationPort"), // only for port mode
destination: text("destination").notNull(), // ip, cidr, hostname
destination: text("destination"), // ip, cidr, hostname
enabled: integer("enabled", { mode: "boolean" }).notNull().default(true),
alias: text("alias"),
aliasAddress: text("aliasAddress"),
@@ -293,8 +434,11 @@ export const siteResources = sqliteTable("siteResources", {
.notNull()
.default(false),
authDaemonPort: integer("authDaemonPort").default(22123),
pamMode: text("pamMode")
.$type<"passthrough" | "push">()
.default("passthrough"),
authDaemonMode: text("authDaemonMode")
.$type<"site" | "remote">()
.$type<"site" | "remote" | "native">()
.default("site"),
domainId: text("domainId").references(() => domains.domainId, {
onDelete: "set null"
@@ -907,6 +1051,47 @@ export const resourceHeaderAuth = sqliteTable("resourceHeaderAuth", {
headerAuthHash: text("headerAuthHash").notNull()
});
export const resourcePolicyPincode = sqliteTable("resourcePolicyPincode", {
pincodeId: integer("pincodeId").primaryKey({ autoIncrement: true }),
pincodeHash: text("pincodeHash").notNull(),
digitLength: integer("digitLength").notNull(),
resourcePolicyId: integer("resourcePolicyId")
.notNull()
.references(() => resourcePolicies.resourcePolicyId, {
onDelete: "cascade"
})
});
export const resourcePolicyPassword = sqliteTable("resourcePolicyPassword", {
passwordId: integer("passwordId").primaryKey({ autoIncrement: true }),
passwordHash: text("passwordHash").notNull(),
resourcePolicyId: integer("resourcePolicyId")
.notNull()
.references(() => resourcePolicies.resourcePolicyId, {
onDelete: "cascade"
})
});
export const resourcePolicyHeaderAuth = sqliteTable(
"resourcePolicyHeaderAuth",
{
headerAuthId: integer("headerAuthId").primaryKey({
autoIncrement: true
}),
headerAuthHash: text("headerAuthHash").notNull(),
extendedCompatibility: integer("extendedCompatibility", {
mode: "boolean"
})
.notNull()
.default(true),
resourcePolicyId: integer("resourcePolicyId")
.notNull()
.references(() => resourcePolicies.resourcePolicyId, {
onDelete: "cascade"
})
}
);
export const resourceHeaderAuthExtendedCompatibility = sqliteTable(
"resourceHeaderAuthExtendedCompatibility",
{
@@ -935,6 +1120,7 @@ export const resourceAccessToken = sqliteTable("resourceAccessToken", {
resourceId: integer("resourceId")
.notNull()
.references(() => resources.resourceId, { onDelete: "cascade" }),
path: text("path"),
tokenHash: text("tokenHash").notNull(),
sessionLength: integer("sessionLength").notNull(),
expiresAt: integer("expiresAt"),
@@ -981,6 +1167,24 @@ export const resourceSessions = sqliteTable("resourceSessions", {
onDelete: "cascade"
}
),
policyPasswordId: integer("policyPasswordId").references(
() => resourcePolicyPassword.passwordId,
{
onDelete: "cascade"
}
),
policyPincodeId: integer("policyPincodeId").references(
() => resourcePolicyPincode.pincodeId,
{
onDelete: "cascade"
}
),
policyWhitelistId: integer("policyWhitelistId").references(
() => resourcePolicyWhiteList.whitelistId,
{
onDelete: "cascade"
}
),
issuedAt: integer("issuedAt")
});
@@ -1021,6 +1225,79 @@ export const resourceRules = sqliteTable("resourceRules", {
value: text("value").notNull()
});
export const rolePolicies = sqliteTable("rolePolicies", {
roleId: integer("roleId")
.notNull()
.references(() => roles.roleId, { onDelete: "cascade" }),
resourcePolicyId: integer("resourcePolicyId")
.notNull()
.references(() => resourcePolicies.resourcePolicyId, {
onDelete: "cascade"
})
});
export const userPolicies = sqliteTable("userPolicies", {
userId: text("userId")
.notNull()
.references(() => users.userId, { onDelete: "cascade" }),
resourcePolicyId: integer("resourcePolicyId")
.notNull()
.references(() => resourcePolicies.resourcePolicyId, {
onDelete: "cascade"
})
});
export const resourcePolicyWhiteList = sqliteTable("resourcePolicyWhitelist", {
whitelistId: integer("id").primaryKey({ autoIncrement: true }),
email: text("email").notNull(),
resourcePolicyId: integer("resourcePolicyId")
.notNull()
.references(() => resourcePolicies.resourcePolicyId, {
onDelete: "cascade"
})
});
export const resourcePolicyRules = sqliteTable("resourcePolicyRules", {
ruleId: integer("ruleId").primaryKey({ autoIncrement: true }),
resourcePolicyId: integer("resourcePolicyId")
.notNull()
.references(() => resourcePolicies.resourcePolicyId, {
onDelete: "cascade"
}),
enabled: integer("enabled", { mode: "boolean" }).notNull().default(true),
priority: integer("priority").notNull(),
action: text("action").$type<"ACCEPT" | "DROP" | "PASS">().notNull(),
match: text("match")
.$type<"CIDR" | "PATH" | "IP" | "COUNTRY" | "ASN" | "REGION">()
.notNull(),
value: text("value").notNull()
});
export const resourcePolicies = sqliteTable("resourcePolicies", {
resourcePolicyId: integer("resourcePolicyId").primaryKey(),
sso: integer("sso", { mode: "boolean" }).notNull().default(true),
applyRules: integer("applyRules", { mode: "boolean" })
.notNull()
.default(false),
scope: text("scope")
.$type<"global" | "resource">()
.notNull()
.default("global"),
emailWhitelistEnabled: integer("emailWhitelistEnabled", { mode: "boolean" })
.notNull()
.default(false),
niceId: text("niceId").notNull(),
idpId: integer("idpId").references(() => idp.idpId, {
onDelete: "set null"
}),
name: text("name").notNull(),
orgId: text("orgId")
.references(() => orgs.orgId, {
onDelete: "cascade"
})
.notNull()
});
export const supporterKey = sqliteTable("supporterKey", {
keyId: integer("keyId").primaryKey({ autoIncrement: true }),
key: text("key").notNull(),
@@ -1194,19 +1471,30 @@ export const roundTripMessageTracker = sqliteTable("roundTripMessageTracker", {
complete: integer("complete", { mode: "boolean" }).notNull().default(false)
});
export const statusHistory = sqliteTable("statusHistory", {
id: integer("id").primaryKey({ autoIncrement: true }),
entityType: text("entityType").notNull(), // "site" | "healthCheck"
entityId: integer("entityId").notNull(), // siteId or targetHealthCheckId
orgId: text("orgId")
.notNull()
.references(() => orgs.orgId, { onDelete: "cascade" }),
status: text("status").notNull(), // "online"/"offline" for sites; "healthy"/"unhealthy"/"unknown" for healthChecks
timestamp: integer("timestamp").notNull(), // unix epoch seconds
}, (table) => [
index("idx_statusHistory_entity").on(table.entityType, table.entityId, table.timestamp),
index("idx_statusHistory_org_timestamp").on(table.orgId, table.timestamp),
]);
export const statusHistory = sqliteTable(
"statusHistory",
{
id: integer("id").primaryKey({ autoIncrement: true }),
entityType: text("entityType").notNull(), // "site" | "healthCheck"
entityId: integer("entityId").notNull(), // siteId or targetHealthCheckId
orgId: text("orgId")
.notNull()
.references(() => orgs.orgId, { onDelete: "cascade" }),
status: text("status").notNull(), // "online"/"offline" for sites; "healthy"/"unhealthy"/"unknown" for healthChecks
timestamp: integer("timestamp").notNull() // unix epoch seconds
},
(table) => [
index("idx_statusHistory_entity").on(
table.entityType,
table.entityId,
table.timestamp
),
index("idx_statusHistory_org_timestamp").on(
table.orgId,
table.timestamp
)
]
);
export type Org = InferSelectModel<typeof orgs>;
export type User = InferSelectModel<typeof users>;
@@ -1276,3 +1564,17 @@ export type RoundTripMessageTracker = InferSelectModel<
typeof roundTripMessageTracker
>;
export type StatusHistory = InferSelectModel<typeof statusHistory>;
export type Label = InferSelectModel<typeof labels>;
export type LauncherView = InferSelectModel<typeof launcherViews>;
export type ResourcePolicy = InferSelectModel<typeof resourcePolicies>;
export type ResourcePolicyPincode = InferSelectModel<
typeof resourcePolicyPincode
>;
export type ResourcePolicyPassword = InferSelectModel<
typeof resourcePolicyPassword
>;
export type ResourcePolicyHeaderAuth = InferSelectModel<
typeof resourcePolicyHeaderAuth
>;
export type RolePolicy = InferSelectModel<typeof rolePolicies>;
export type UserPolicy = InferSelectModel<typeof userPolicies>;

View File

@@ -23,6 +23,7 @@ export type AlertEventType =
| "health_check_toggle"
| "resource_healthy"
| "resource_unhealthy"
| "resource_degraded"
| "resource_toggle";
export type AlertNotificationProps = {
@@ -36,8 +37,8 @@ function getEventMeta(eventType: AlertEventType): {
heading: string;
previewText: string;
summary: string;
statusLabel: string;
statusColor: string;
statusLabel: string | null;
statusColor: string | null;
} {
switch (eventType) {
case "site_online":
@@ -63,8 +64,8 @@ function getEventMeta(eventType: AlertEventType): {
heading: "Site Status Changed",
previewText: "A site in your organization has changed status.",
summary: "A site in your organization has changed status.",
statusLabel: "Status Changed",
statusColor: "#f59e0b"
statusLabel: null,
statusColor: null
};
case "health_check_healthy":
return {
@@ -93,8 +94,8 @@ function getEventMeta(eventType: AlertEventType): {
"A health check in your organization has changed status.",
summary:
"A health check in your organization has changed status.",
statusLabel: "Status Changed",
statusColor: "#f59e0b"
statusLabel: null,
statusColor: null
};
case "resource_healthy":
return {
@@ -114,14 +115,23 @@ function getEventMeta(eventType: AlertEventType): {
statusLabel: "Unhealthy",
statusColor: "#dc2626"
};
case "resource_degraded":
return {
heading: "Resource Degraded",
previewText: "A resource in your organization is degraded.",
summary:
"A resource in your organization is currently degraded.",
statusLabel: "Degraded",
statusColor: "#dc2626"
};
case "resource_toggle":
return {
heading: "Resource Status Changed",
previewText:
"A resource in your organization has changed status.",
summary: "A resource in your organization has changed status.",
statusLabel: "Status Changed",
statusColor: "#f59e0b"
statusLabel: null,
statusColor: null
};
default:
return {
@@ -135,11 +145,31 @@ function getEventMeta(eventType: AlertEventType): {
}
}
function resolveToggleStatus(status: unknown): {
label: string;
color: string;
} {
switch (String(status).toLowerCase()) {
case "online":
return { label: "Online", color: "#16a34a" };
case "offline":
return { label: "Offline", color: "#dc2626" };
case "healthy":
return { label: "Healthy", color: "#16a34a" };
case "unhealthy":
return { label: "Unhealthy", color: "#dc2626" };
case "degraded":
return { label: "Degraded", color: "#dc2626" };
default:
return { label: String(status ?? "Unknown"), color: "#f59e0b" };
}
}
function formatDataItems(
data: Record<string, unknown>
): { label: string; value: React.ReactNode }[] {
return Object.entries(data)
.filter(([key]) => key !== "orgId")
.filter(([key]) => key !== "orgId" && key !== "status")
.map(([key, value]) => ({
label: key
.replace(/([A-Z])/g, " $1")
@@ -154,16 +184,36 @@ export const AlertNotification = (props: AlertNotificationProps) => {
const meta = getEventMeta(eventType);
const dataItems = formatDataItems(data);
const isToggle =
eventType === "site_toggle" ||
eventType === "health_check_toggle" ||
eventType === "resource_toggle";
const resolvedStatus = isToggle
? resolveToggleStatus(data.status)
: meta.statusLabel != null
? { label: meta.statusLabel, color: meta.statusColor! }
: null;
const allItems: { label: string; value: React.ReactNode }[] = [
{ label: "Organization", value: orgId },
{
label: "Status",
value: (
<span style={{ color: meta.statusColor, fontWeight: 600 }}>
{meta.statusLabel}
</span>
)
},
...(resolvedStatus != null
? [
{
label: "Status",
value: (
<span
style={{
color: resolvedStatus.color,
fontWeight: 600
}}
>
{resolvedStatus.label}
</span>
)
}
]
: []),
{ label: "Time", value: new Date().toUTCString() },
...dataItems
];

View File

@@ -64,7 +64,7 @@ export const NotifyTrialExpiring = ({
<EmailText>
Some features and resources may now be
restricted or disconnected. To restore full
restricted. To restore full
access and continue using all the features
you had during your trial, please upgrade to
a paid plan.
@@ -85,7 +85,7 @@ export const NotifyTrialExpiring = ({
<strong>{orgName}</strong> will end on{" "}
<strong>{trialEndsAt}</strong>
{isLastDay
? " that's tomorrow!"
? " - that's tomorrow!"
: `, in ${daysRemaining} days`}
.
</EmailText>
@@ -93,8 +93,7 @@ export const NotifyTrialExpiring = ({
<EmailText>
After your trial ends, your account will be
moved to the free plan and some
functionality may be restricted or your
sites may disconnect.
functionality may be restricted.
</EmailText>
<EmailText>

View File

@@ -1,5 +1,5 @@
#! /usr/bin/env node
import "./extendZod.ts";
import "./extendZod";
import { runSetupFunctions } from "./setup";
import { createApiServer } from "./apiServer";
@@ -24,6 +24,7 @@ import license from "#dynamic/license/license";
import { initLogCleanupInterval } from "@server/lib/cleanupLogs";
import { initAcmeCertSync } from "#dynamic/lib/acmeCertSync";
import { fetchServerIp } from "@server/lib/serverIpService";
import { startRebuildQueueProcessor } from "@server/lib/rebuildClientAssociations";
async function startServers() {
await setHostMeta();
@@ -41,6 +42,7 @@ async function startServers() {
initLogCleanupInterval();
initAcmeCertSync();
startRebuildQueueProcessor();
// Start all servers
const apiServer = createApiServer();

View File

@@ -152,11 +152,19 @@ function getOpenApiDocumentation() {
if (!hasExistingResponses) {
def.route.responses = {
"*": {
description: "",
"200": {
description: "Successful response",
content: {
"application/json": {
schema: z.object({})
schema: z.object({
data: z
.record(z.string(), z.any())
.nullable(),
success: z.boolean(),
error: z.boolean(),
message: z.string(),
status: z.number()
})
}
}
}

View File

@@ -1,19 +1,301 @@
// stub
import logger from "@server/logger";
import { processAlerts } from "#dynamic/lib/alerts";
import {
db,
statusHistory,
targetHealthCheck,
targets,
resources,
Transaction,
logsDb
} from "@server/db";
import { eq } from "drizzle-orm";
import { invalidateStatusHistoryCache } from "@server/lib/statusHistory";
import {
fireResourceDegradedAlert,
fireResourceHealthyAlert,
fireResourceUnhealthyAlert,
fireResourceUnknownAlert
} from "./resourceEvents";
// ---------------------------------------------------------------------------
// Public API
// ---------------------------------------------------------------------------
/**
* Fire a `health_check_healthy` alert for the given health check.
*
* Call this after a previously-failing health check has recovered so that any
* matching `alertRules` can dispatch their email and webhook actions.
*
* @param orgId - Organisation that owns the health check.
* @param healthCheckId - Numeric primary key of the health check.
* @param healthCheckName - Human-readable name shown in notifications (optional).
* @param extra - Any additional key/value pairs to include in the payload.
*/
export async function fireHealthCheckHealthyAlert(
orgId: string,
healthCheckId: number,
healthCheckName?: string,
extra?: Record<string, unknown>
healthCheckName?: string | null,
healthCheckTargetId?: number | null,
extra?: Record<string, unknown>,
send: boolean = true,
trx: Transaction | typeof db = db
): Promise<void> {
return;
try {
await logsDb.insert(statusHistory).values({
entityType: "health_check",
entityId: healthCheckId,
orgId: orgId,
status: "healthy",
timestamp: Math.floor(Date.now() / 1000)
});
await invalidateStatusHistoryCache("health_check", healthCheckId);
await handleResource(orgId, healthCheckTargetId, send, trx);
if (!send) {
return;
}
await processAlerts({
eventType: "health_check_healthy",
orgId,
healthCheckId,
data: {
...(healthCheckName != null ? { healthCheckName } : {}),
...extra
}
});
await processAlerts({
eventType: "health_check_toggle",
orgId,
healthCheckId,
data: {
healthCheckId,
status: "healthy",
...(healthCheckName != null ? { healthCheckName } : {}),
...extra
}
});
} catch (err) {
logger.error(
`fireHealthCheckHealthyAlert: unexpected error for healthCheckId ${healthCheckId}`,
err
);
}
}
/**
* Fire a `health_check_unhealthy` alert for the given health check.
*
* Call this after a health check has been detected as failing so that any
* matching `alertRules` can dispatch their email and webhook actions.
*
* @param orgId - Organisation that owns the health check.
* @param healthCheckId - Numeric primary key of the health check.
* @param healthCheckName - Human-readable name shown in notifications (optional).
* @param extra - Any additional key/value pairs to include in the payload.
*/
export async function fireHealthCheckUnhealthyAlert(
orgId: string,
healthCheckId: number,
healthCheckName?: string,
extra?: Record<string, unknown>
healthCheckName?: string | null,
healthCheckTargetId?: number | null,
extra?: Record<string, unknown>,
send: boolean = true,
trx: Transaction | typeof db = db
): Promise<void> {
return;
try {
await logsDb.insert(statusHistory).values({
entityType: "health_check",
entityId: healthCheckId,
orgId: orgId,
status: "unhealthy",
timestamp: Math.floor(Date.now() / 1000)
});
await invalidateStatusHistoryCache("health_check", healthCheckId);
await handleResource(orgId, healthCheckTargetId, send, trx);
if (!send) {
return;
}
await processAlerts({
eventType: "health_check_unhealthy",
orgId,
healthCheckId,
data: {
...(healthCheckName != null ? { healthCheckName } : {}),
...extra
}
});
await processAlerts({
eventType: "health_check_toggle",
orgId,
healthCheckId,
data: {
healthCheckId,
status: "unhealthy",
...(healthCheckName != null ? { healthCheckName } : {}),
...extra
}
});
} catch (err) {
logger.error(
`fireHealthCheckUnhealthyAlert: unexpected error for healthCheckId ${healthCheckId}`,
err
);
}
}
export async function fireHealthCheckUnknownAlert(
orgId: string,
healthCheckId: number,
healthCheckName?: string | null,
healthCheckTargetId?: number | null,
extra?: Record<string, unknown>,
send: boolean = true,
trx: Transaction | typeof db = db
): Promise<void> {
try {
await logsDb.insert(statusHistory).values({
entityType: "health_check",
entityId: healthCheckId,
orgId: orgId,
status: "unknown",
timestamp: Math.floor(Date.now() / 1000)
});
await invalidateStatusHistoryCache("health_check", healthCheckId);
await handleResource(orgId, healthCheckTargetId, send, trx);
if (!send) {
return;
}
} catch (err) {
logger.error(
`fireHealthCheckUnknownAlert: unexpected error for healthCheckId ${healthCheckId}`,
err
);
}
}
async function handleResource(
orgId: string,
healthCheckTargetId?: number | null,
send: boolean = true,
trx: Transaction | typeof db = db
) {
if (!healthCheckTargetId) {
return;
}
// we have targets lets get them
const [target] = await trx
.select()
.from(targets)
.where(eq(targets.targetId, healthCheckTargetId))
.limit(1);
if (!target) {
return;
}
const [resource] = await trx
.select()
.from(resources)
.where(eq(resources.resourceId, target.resourceId))
.limit(1);
if (!resource) {
return;
}
const otherTargets = await trx
.select({ hcHealth: targetHealthCheck.hcHealth })
.from(targets)
.innerJoin(
targetHealthCheck,
eq(targetHealthCheck.targetId, targets.targetId)
)
.where(eq(targets.resourceId, resource.resourceId));
const monitoredTargets = otherTargets.filter(
(t) => t.hcHealth !== "unknown"
);
let health = "healthy";
const allUnknown = monitoredTargets.length === 0;
const allHealthy = monitoredTargets.every(
(t) => t.hcHealth === "healthy"
);
const allUnhealthy = monitoredTargets.every(
(t) => t.hcHealth === "unhealthy"
);
if (allUnknown) {
logger.debug(
`Marking resource ${resource.resourceId} as unknown because all health checks are disabled`
);
health = "unknown";
} else if (allHealthy) {
health = "healthy";
} else if (allUnhealthy) {
logger.debug(
`Marking resource ${resource.resourceId} as unhealthy because all targets are unhealthy`
);
health = "unhealthy";
} else {
logger.debug(
`Marking resource ${resource.resourceId} as degraded because some targets are unhealthy`
);
health = "degraded";
}
if (health != resource.health) {
// it changed
await trx
.update(resources)
.set({ health })
.where(eq(resources.resourceId, resource.resourceId));
if (health === "unknown") {
await fireResourceUnknownAlert(
orgId,
resource.resourceId,
resource.name,
undefined,
send,
trx
);
} else if (health === "unhealthy") {
await fireResourceUnhealthyAlert(
orgId,
resource.resourceId,
resource.name,
undefined,
send,
trx
);
} else if (health === "healthy") {
await fireResourceHealthyAlert(
orgId,
resource.resourceId,
resource.name,
undefined,
send,
trx
);
} else if (health === "degraded") {
await fireResourceDegradedAlert(
orgId,
resource.resourceId,
resource.name,
undefined,
send,
trx
);
}
}
}

View File

@@ -1,20 +1,243 @@
import logger from "@server/logger";
import { processAlerts } from "#dynamic/lib/alerts";
import { db, logsDb, statusHistory, Transaction } from "@server/db";
import { invalidateStatusHistoryCache } from "@server/lib/statusHistory";
// ---------------------------------------------------------------------------
// Public API
// ---------------------------------------------------------------------------
/**
* Fire a `resource_healthy` alert for the given resource.
*
* Call this after a previously-unhealthy resource has recovered so that any
* matching `alertRules` can dispatch their email and webhook actions.
*
* @param orgId - Organisation that owns the resource.
* @param resourceId - Numeric primary key of the resource.
* @param resourceName - Human-readable name shown in notifications (optional).
* @param extra - Any additional key/value pairs to include in the payload.
*/
export async function fireResourceHealthyAlert(
orgId: string,
resourceId: number,
resourceName?: string | null,
extra?: Record<string, unknown>
): Promise<void> {}
extra?: Record<string, unknown>,
send: boolean = true,
trx: Transaction | typeof db = db
): Promise<void> {
try {
await logsDb.insert(statusHistory).values({
entityType: "resource",
entityId: resourceId,
orgId: orgId,
status: "healthy",
timestamp: Math.floor(Date.now() / 1000)
});
await invalidateStatusHistoryCache("resource", resourceId);
if (!send) {
return;
}
await processAlerts({
eventType: "resource_healthy",
orgId,
resourceId,
data: {
...(resourceName != null ? { resourceName } : {}),
...extra
}
});
await processAlerts({
eventType: "resource_toggle",
orgId,
resourceId,
data: {
resourceId,
status: "healthy",
...(resourceName != null ? { resourceName } : {}),
...extra
}
});
} catch (err) {
logger.error(
`fireResourceHealthyAlert: unexpected error for resourceId ${resourceId}`,
err
);
}
}
/**
* Fire a `resource_unhealthy` alert for the given resource.
*
* Call this after a resource has been detected as unhealthy so that any
* matching `alertRules` can dispatch their email and webhook actions.
*
* @param orgId - Organisation that owns the resource.
* @param resourceId - Numeric primary key of the resource.
* @param resourceName - Human-readable name shown in notifications (optional).
* @param extra - Any additional key/value pairs to include in the payload.
*/
export async function fireResourceUnhealthyAlert(
orgId: string,
resourceId: number,
resourceName?: string | null,
extra?: Record<string, unknown>
): Promise<void> {}
extra?: Record<string, unknown>,
send: boolean = true,
trx: Transaction | typeof db = db
): Promise<void> {
try {
await logsDb.insert(statusHistory).values({
entityType: "resource",
entityId: resourceId,
orgId: orgId,
status: "unhealthy",
timestamp: Math.floor(Date.now() / 1000)
});
await invalidateStatusHistoryCache("resource", resourceId);
export async function fireResourceToggleAlert(
if (!send) {
return;
}
await processAlerts({
eventType: "resource_unhealthy",
orgId,
resourceId,
data: {
...(resourceName != null ? { resourceName } : {}),
...extra
}
});
await processAlerts({
eventType: "resource_toggle",
orgId,
resourceId,
data: {
resourceId,
status: "unhealthy",
...(resourceName != null ? { resourceName } : {}),
...extra
}
});
} catch (err) {
logger.error(
`fireResourceUnhealthyAlert: unexpected error for resourceId ${resourceId}`,
err
);
}
}
/**
* Fire a `resource_degraded` alert for the given resource.
*
* Call this after a resource has been detected as degraded so that any
* matching `alertRules` can dispatch their email and webhook actions.
*
* @param orgId - Organisation that owns the resource.
* @param resourceId - Numeric primary key of the resource.
* @param resourceName - Human-readable name shown in notifications (optional).
* @param extra - Any additional key/value pairs to include in the payload.
*/
export async function fireResourceDegradedAlert(
orgId: string,
resourceId: number,
resourceName?: string | null,
extra?: Record<string, unknown>
): Promise<void> {}
extra?: Record<string, unknown>,
send: boolean = true,
trx: Transaction | typeof db = db
): Promise<void> {
try {
await logsDb.insert(statusHistory).values({
entityType: "resource",
entityId: resourceId,
orgId: orgId,
status: "degraded",
timestamp: Math.floor(Date.now() / 1000)
});
await invalidateStatusHistoryCache("resource", resourceId);
if (!send) {
return;
}
await processAlerts({
eventType: "resource_degraded",
orgId,
resourceId,
data: {
...(resourceName != null ? { resourceName } : {}),
...extra
}
});
await processAlerts({
eventType: "resource_toggle",
orgId,
resourceId,
data: {
resourceId,
status: "degraded",
...(resourceName != null ? { resourceName } : {}),
...extra
}
});
} catch (err) {
logger.error(
`fireResourceDegradedAlert: unexpected error for resourceId ${resourceId}`,
err
);
}
}
/**
* Fire a `resource_unknown` alert for the given resource.
*
* Call this when all health checks on a resource are disabled so that the
* resource status transitions to unknown.
*
* @param orgId - Organisation that owns the resource.
* @param resourceId - Numeric primary key of the resource.
* @param resourceName - Human-readable name shown in notifications (optional).
* @param extra - Any additional key/value pairs to include in the payload.
*/
export async function fireResourceUnknownAlert(
orgId: string,
resourceId: number,
resourceName?: string | null,
extra?: Record<string, unknown>,
send: boolean = true,
trx: Transaction | typeof db = db
): Promise<void> {
try {
await logsDb.insert(statusHistory).values({
entityType: "resource",
entityId: resourceId,
orgId: orgId,
status: "unknown",
timestamp: Math.floor(Date.now() / 1000)
});
await invalidateStatusHistoryCache("resource", resourceId);
if (!send) {
return;
}
await processAlerts({
eventType: "resource_toggle",
orgId,
resourceId,
data: {
resourceId,
status: "unknown",
...(resourceName != null ? { resourceName } : {}),
...extra
}
});
} catch (err) {
logger.error(
`fireResourceUnknownAlert: unexpected error for resourceId ${resourceId}`,
err
);
}
}

View File

@@ -1,19 +1,156 @@
// stub
import logger from "@server/logger";
import { processAlerts } from "#dynamic/lib/alerts";
import {
db,
logsDb,
statusHistory,
targetHealthCheck,
Transaction
} from "@server/db";
import { invalidateStatusHistoryCache } from "@server/lib/statusHistory";
import { and, eq, inArray } from "drizzle-orm";
import { fireHealthCheckUnhealthyAlert } from "./healthCheckEvents";
// ---------------------------------------------------------------------------
// Public API
// ---------------------------------------------------------------------------
/**
* Fire a `site_online` alert for the given site.
*
* Call this after the site has been confirmed reachable / connected so that
* any matching `alertRules` can dispatch their email and webhook actions.
*
* @param orgId - Organisation that owns the site.
* @param siteId - Numeric primary key of the site.
* @param siteName - Human-readable name shown in notifications (optional).
* @param extra - Any additional key/value pairs to include in the payload.
*/
export async function fireSiteOnlineAlert(
orgId: string,
siteId: number,
siteName?: string,
extra?: Record<string, unknown>
extra?: Record<string, unknown>,
trx: Transaction | typeof db = db
): Promise<void> {
return;
try {
await logsDb.insert(statusHistory).values({
entityType: "site",
entityId: siteId,
orgId: orgId,
status: "online",
timestamp: Math.floor(Date.now() / 1000)
});
await invalidateStatusHistoryCache("site", siteId);
await processAlerts({
eventType: "site_online",
orgId,
siteId,
data: {
...(siteName != null ? { siteName } : {}),
...extra
}
});
await processAlerts({
eventType: "site_toggle",
orgId,
siteId,
data: {
siteId,
status: "online",
...(siteName != null ? { siteName } : {}),
...extra
}
});
} catch (err) {
logger.error(
`fireSiteOnlineAlert: unexpected error for siteId ${siteId}`,
err
);
}
}
/**
* Fire a `site_offline` alert for the given site.
*
* Call this after the site has been detected as unreachable / disconnected so
* that any matching `alertRules` can dispatch their email and webhook actions.
*
* @param orgId - Organisation that owns the site.
* @param siteId - Numeric primary key of the site.
* @param siteName - Human-readable name shown in notifications (optional).
* @param extra - Any additional key/value pairs to include in the payload.
*/
export async function fireSiteOfflineAlert(
orgId: string,
siteId: number,
siteName?: string,
extra?: Record<string, unknown>
extra?: Record<string, unknown>,
trx: Transaction | typeof db = db
): Promise<void> {
return;
try {
await logsDb.insert(statusHistory).values({
entityType: "site",
entityId: siteId,
orgId: orgId,
status: "offline",
timestamp: Math.floor(Date.now() / 1000)
});
await invalidateStatusHistoryCache("site", siteId);
const unhealthyHealthChecks = await trx
.update(targetHealthCheck)
.set({ hcHealth: "unhealthy" })
.where(
and(
eq(targetHealthCheck.orgId, orgId),
eq(targetHealthCheck.siteId, siteId),
eq(targetHealthCheck.hcEnabled, true) // only effect the ones that are enabled
)
)
.returning();
for (const healthCheck of unhealthyHealthChecks) {
logger.info(
`Marking health check ${healthCheck.targetHealthCheckId} unhealthy due to site ${siteId} being marked offline`
);
await fireHealthCheckUnhealthyAlert(
healthCheck.orgId,
healthCheck.targetHealthCheckId,
healthCheck.name,
healthCheck.targetId, // for the resource if we have one
undefined,
true,
trx
);
}
await processAlerts({
eventType: "site_offline",
orgId,
siteId,
data: {
...(siteName != null ? { siteName } : {}),
...extra
}
});
await processAlerts({
eventType: "site_toggle",
orgId,
siteId,
data: {
siteId,
status: "offline",
...(siteName != null ? { siteName } : {}),
...extra
}
});
} catch (err) {
logger.error(
`fireSiteOfflineAlert: unexpected error for siteId ${siteId}`,
err
);
}
}

View File

@@ -1,3 +1,4 @@
export * from "./events/siteEvents";
export * from "./events/healthCheckEvents";
export * from "./events/resourceEvents";
export * from "./processAlerts";

View File

@@ -0,0 +1,5 @@
import { AlertContext } from "@server/routers/alertRule/types";
export async function processAlerts(context: AlertContext): Promise<void> {
return;
}

View File

@@ -1,28 +1,39 @@
export enum FeatureId {
export enum LimitId {
USERS = "users",
SITES = "sites",
EGRESS_DATA_MB = "egressDataMb",
DOMAINS = "domains",
REMOTE_EXIT_NODES = "remoteExitNodes",
ORGINIZATIONS = "organizations",
ORGANIZATIONS = "organizations",
PUBLIC_RESOURCES = "publicResources",
PRIVATE_RESOURCES = "privateResources",
MACHINE_CLIENTS = "machineClients",
TIER1 = "tier1"
}
export async function getFeatureDisplayName(featureId: FeatureId): Promise<string> {
export async function getFeatureDisplayName(
featureId: LimitId
): Promise<string> {
switch (featureId) {
case FeatureId.USERS:
case LimitId.USERS:
return "Users";
case FeatureId.SITES:
case LimitId.SITES:
return "Sites";
case FeatureId.EGRESS_DATA_MB:
case LimitId.EGRESS_DATA_MB:
return "Egress Data (MB)";
case FeatureId.DOMAINS:
case LimitId.DOMAINS:
return "Domains";
case FeatureId.REMOTE_EXIT_NODES:
case LimitId.REMOTE_EXIT_NODES:
return "Remote Exit Nodes";
case FeatureId.ORGINIZATIONS:
case LimitId.ORGANIZATIONS:
return "Organizations";
case FeatureId.TIER1:
case LimitId.PUBLIC_RESOURCES:
return "Public Resources";
case LimitId.PRIVATE_RESOURCES:
return "Private Resources";
case LimitId.MACHINE_CLIENTS:
return "Machine Clients";
case LimitId.TIER1:
return "Home Lab";
default:
return featureId;
@@ -30,15 +41,16 @@ export async function getFeatureDisplayName(featureId: FeatureId): Promise<strin
}
// this is from the old system
export const FeatureMeterIds: Partial<Record<FeatureId, string>> = { // right now we are not charging for any data
export const FeatureMeterIds: Partial<Record<LimitId, string>> = {
// right now we are not charging for any data
// [FeatureId.EGRESS_DATA_MB]: "mtr_61Srreh9eWrExDSCe41D3Ee2Ir7Wm5YW"
};
export const FeatureMeterIdsSandbox: Partial<Record<FeatureId, string>> = {
export const FeatureMeterIdsSandbox: Partial<Record<LimitId, string>> = {
// [FeatureId.EGRESS_DATA_MB]: "mtr_test_61Snh2a2m6qome5Kv41DCpkOb237B3dQ"
};
export function getFeatureMeterId(featureId: FeatureId): string | undefined {
export function getFeatureMeterId(featureId: LimitId): string | undefined {
if (
process.env.ENVIRONMENT == "prod" &&
process.env.SANDBOX_MODE !== "true"
@@ -49,22 +61,20 @@ export function getFeatureMeterId(featureId: FeatureId): string | undefined {
}
}
export function getFeatureIdByMetricId(
metricId: string
): FeatureId | undefined {
return (Object.entries(FeatureMeterIds) as [FeatureId, string][]).find(
export function getFeatureIdByMetricId(metricId: string): LimitId | undefined {
return (Object.entries(FeatureMeterIds) as [LimitId, string][]).find(
([_, v]) => v === metricId
)?.[0];
}
export type FeaturePriceSet = Partial<Record<FeatureId, string>>;
export type FeaturePriceSet = Partial<Record<LimitId, string>>;
export const tier1FeaturePriceSet: FeaturePriceSet = {
[FeatureId.TIER1]: "price_1SzVE3D3Ee2Ir7Wm6wT5Dl3G"
[LimitId.TIER1]: "price_1SzVE3D3Ee2Ir7Wm6wT5Dl3G"
};
export const tier1FeaturePriceSetSandbox: FeaturePriceSet = {
[FeatureId.TIER1]: "price_1SxgpPDCpkOb237Bfo4rIsoT"
[LimitId.TIER1]: "price_1SxgpPDCpkOb237Bfo4rIsoT"
};
export function getTier1FeaturePriceSet(): FeaturePriceSet {
@@ -79,11 +89,11 @@ export function getTier1FeaturePriceSet(): FeaturePriceSet {
}
export const tier2FeaturePriceSet: FeaturePriceSet = {
[FeatureId.USERS]: "price_1SzVCcD3Ee2Ir7Wmn6U3KvPN"
[LimitId.USERS]: "price_1SzVCcD3Ee2Ir7Wmn6U3KvPN"
};
export const tier2FeaturePriceSetSandbox: FeaturePriceSet = {
[FeatureId.USERS]: "price_1SxaEHDCpkOb237BD9lBkPiR"
[LimitId.USERS]: "price_1SxaEHDCpkOb237BD9lBkPiR"
};
export function getTier2FeaturePriceSet(): FeaturePriceSet {
@@ -98,11 +108,11 @@ export function getTier2FeaturePriceSet(): FeaturePriceSet {
}
export const tier3FeaturePriceSet: FeaturePriceSet = {
[FeatureId.USERS]: "price_1SzVDKD3Ee2Ir7WmPtOKNusv"
[LimitId.USERS]: "price_1SzVDKD3Ee2Ir7WmPtOKNusv"
};
export const tier3FeaturePriceSetSandbox: FeaturePriceSet = {
[FeatureId.USERS]: "price_1SxaEODCpkOb237BiXdCBSfs"
[LimitId.USERS]: "price_1SxaEODCpkOb237BiXdCBSfs"
};
export function getTier3FeaturePriceSet(): FeaturePriceSet {
@@ -116,7 +126,7 @@ export function getTier3FeaturePriceSet(): FeaturePriceSet {
}
}
export function getFeatureIdByPriceId(priceId: string): FeatureId | undefined {
export function getFeatureIdByPriceId(priceId: string): LimitId | undefined {
// Check all feature price sets
const allPriceSets = [
getTier1FeaturePriceSet(),
@@ -125,7 +135,7 @@ export function getFeatureIdByPriceId(priceId: string): FeatureId | undefined {
];
for (const priceSet of allPriceSets) {
const entry = (Object.entries(priceSet) as [FeatureId, string][]).find(
const entry = (Object.entries(priceSet) as [LimitId, string][]).find(
([_, price]) => price === priceId
);
if (entry) {

View File

@@ -1,19 +1,19 @@
import Stripe from "stripe";
import { FeatureId, FeaturePriceSet } from "./features";
import { LimitId, FeaturePriceSet } from "./features";
import { usageService } from "./usageService";
export async function getLineItems(
featurePriceSet: FeaturePriceSet,
orgId: string,
orgId: string
): Promise<Stripe.Checkout.SessionCreateParams.LineItem[]> {
const users = await usageService.getUsage(orgId, FeatureId.USERS);
const users = await usageService.getUsage(orgId, LimitId.USERS);
return Object.entries(featurePriceSet).map(([featureId, priceId]) => {
let quantity: number | undefined;
if (featureId === FeatureId.USERS) {
if (featureId === LimitId.USERS) {
quantity = users?.instantaneousValue || 1;
} else if (featureId === FeatureId.TIER1) {
} else if (featureId === LimitId.TIER1) {
quantity = 1;
}

View File

@@ -1,8 +1,9 @@
export async function getOrgTierData(
orgId: string
): Promise<{ tier: string | null; active: boolean }> {
): Promise<{ tier: string | null; active: boolean; isTrial: boolean }> {
const tier = null;
const active = false;
const isTrial = false;
return { tier, active };
return { tier, active, isTrial };
}

View File

@@ -1,70 +1,82 @@
import { FeatureId } from "./features";
import { LimitId } from "./features";
export type LimitSet = Partial<{
[key in FeatureId]: {
[key in LimitId]: {
value: number | null; // null indicates no limit
description?: string;
};
}>;
export const freeLimitSet: LimitSet = {
[FeatureId.SITES]: { value: 5, description: "Basic limit" },
[FeatureId.USERS]: { value: 5, description: "Basic limit" },
[FeatureId.DOMAINS]: { value: 5, description: "Basic limit" },
[FeatureId.REMOTE_EXIT_NODES]: { value: 1, description: "Basic limit" },
[FeatureId.ORGINIZATIONS]: { value: 1, description: "Basic limit" },
[LimitId.SITES]: { value: 5, description: "Basic limit" },
[LimitId.USERS]: { value: 5, description: "Basic limit" },
[LimitId.DOMAINS]: { value: 5, description: "Basic limit" },
[LimitId.REMOTE_EXIT_NODES]: { value: 1, description: "Basic limit" },
[LimitId.ORGANIZATIONS]: { value: 1, description: "Basic limit" },
[LimitId.PUBLIC_RESOURCES]: { value: 15, description: "Basic limit" },
[LimitId.PRIVATE_RESOURCES]: { value: 15, description: "Basic limit" },
[LimitId.MACHINE_CLIENTS]: { value: 5, description: "Basic limit" }
};
export const tier1LimitSet: LimitSet = {
[FeatureId.USERS]: { value: 7, description: "Home limit" },
[FeatureId.SITES]: { value: 10, description: "Home limit" },
[FeatureId.DOMAINS]: { value: 10, description: "Home limit" },
[FeatureId.REMOTE_EXIT_NODES]: { value: 1, description: "Home limit" },
[FeatureId.ORGINIZATIONS]: { value: 1, description: "Home limit" },
[LimitId.USERS]: { value: 7, description: "Home limit" },
[LimitId.SITES]: { value: 10, description: "Home limit" },
[LimitId.DOMAINS]: { value: 10, description: "Home limit" },
[LimitId.REMOTE_EXIT_NODES]: { value: 1, description: "Home limit" },
[LimitId.ORGANIZATIONS]: { value: 1, description: "Home limit" },
[LimitId.PUBLIC_RESOURCES]: { value: 30, description: "Home limit" },
[LimitId.PRIVATE_RESOURCES]: { value: 30, description: "Home limit" },
[LimitId.MACHINE_CLIENTS]: { value: 10, description: "Home limit" }
};
export const tier2LimitSet: LimitSet = {
[FeatureId.USERS]: {
value: 100,
description: "Team limit"
},
[FeatureId.SITES]: {
[LimitId.USERS]: {
value: 50,
description: "Team limit"
},
[FeatureId.DOMAINS]: {
[LimitId.SITES]: {
value: 50,
description: "Team limit"
},
[FeatureId.REMOTE_EXIT_NODES]: {
[LimitId.DOMAINS]: {
value: 50,
description: "Team limit"
},
[LimitId.REMOTE_EXIT_NODES]: {
value: 3,
description: "Team limit"
},
[FeatureId.ORGINIZATIONS]: {
[LimitId.ORGANIZATIONS]: {
value: 1,
description: "Team limit"
}
},
[LimitId.PUBLIC_RESOURCES]: { value: 150, description: "Team limit" },
[LimitId.PRIVATE_RESOURCES]: { value: 150, description: "Team limit" },
[LimitId.MACHINE_CLIENTS]: { value: 25, description: "Team limit" }
};
export const tier3LimitSet: LimitSet = {
[FeatureId.USERS]: {
value: 500,
description: "Business limit"
},
[FeatureId.SITES]: {
[LimitId.USERS]: {
value: 250,
description: "Business limit"
},
[FeatureId.DOMAINS]: {
[LimitId.SITES]: {
value: 250,
description: "Business limit"
},
[LimitId.DOMAINS]: {
value: 100,
description: "Business limit"
},
[FeatureId.REMOTE_EXIT_NODES]: {
[LimitId.REMOTE_EXIT_NODES]: {
value: 20,
description: "Business limit"
},
[FeatureId.ORGINIZATIONS]: {
[LimitId.ORGANIZATIONS]: {
value: 5,
description: "Business limit"
},
[LimitId.PUBLIC_RESOURCES]: { value: 750, description: "Business limit" },
[LimitId.PRIVATE_RESOURCES]: { value: 750, description: "Business limit" },
[LimitId.MACHINE_CLIENTS]: { value: 100, description: "Business limit" }
};

View File

@@ -1,7 +1,7 @@
import { db, limits } from "@server/db";
import { and, eq } from "drizzle-orm";
import { LimitSet } from "./limitSet";
import { FeatureId } from "./features";
import { LimitId } from "./features";
import logger from "@server/logger";
class LimitService {
@@ -38,7 +38,7 @@ class LimitService {
async getOrgLimit(
orgId: string,
featureId: FeatureId
featureId: LimitId
): Promise<number | null> {
const limitId = `${orgId}-${featureId}`;
const [limit] = await db

View File

@@ -16,17 +16,22 @@ export enum TierFeature {
SessionDurationPolicies = "sessionDurationPolicies", // handle downgrade by setting to default duration
PasswordExpirationPolicies = "passwordExpirationPolicies", // handle downgrade by setting to default duration
AutoProvisioning = "autoProvisioning", // handle downgrade by disabling auto provisioning
SshPam = "sshPam",
FullRbac = "fullRbac",
SiteProvisioningKeys = "siteProvisioningKeys", // handle downgrade by revoking keys if needed
SIEM = "siem", // handle downgrade by disabling SIEM integrations
HTTPPrivateResources = "httpPrivateResources", // handle downgrade by disabling HTTP private resources
DomainNamespaces = "domainNamespaces", // handle downgrade by removing custom domain namespaces
StandaloneHealthChecks = "standaloneHealthChecks",
AlertingRules = "alertingRules"
AlertingRules = "alertingRules",
WildcardSubdomain = "wildcardSubdomain",
Labels = "labels",
NewtAutoUpdate = "newtAutoUpdate",
ResourcePolicies = "resourcePolicies",
AdvancedPublicResources = "advancedPublicResources",
AdvancedPrivateResources = "advancedPrivateResources"
}
export const tierMatrix: Record<TierFeature, Tier[]> = {
[TierFeature.Labels]: ["tier1", "tier2", "tier3", "enterprise"],
[TierFeature.OrgOidc]: ["tier1", "tier2", "tier3", "enterprise"],
[TierFeature.LoginPageDomain]: ["tier1", "tier2", "tier3", "enterprise"],
[TierFeature.DeviceApprovals]: ["tier1", "tier3", "enterprise"],
@@ -57,12 +62,15 @@ export const tierMatrix: Record<TierFeature, Tier[]> = {
"enterprise"
],
[TierFeature.AutoProvisioning]: ["tier1", "tier3", "enterprise"],
[TierFeature.SshPam]: ["tier1", "tier3", "enterprise"],
[TierFeature.FullRbac]: ["tier1", "tier2", "tier3", "enterprise"],
[TierFeature.SiteProvisioningKeys]: ["tier3", "enterprise"],
[TierFeature.SIEM]: ["enterprise"],
[TierFeature.HTTPPrivateResources]: ["tier3", "enterprise"],
[TierFeature.DomainNamespaces]: ["tier1", "tier2", "tier3", "enterprise"],
[TierFeature.StandaloneHealthChecks]: ["tier2", "tier3", "enterprise"],
[TierFeature.AlertingRules]: ["tier2", "tier3", "enterprise"]
[TierFeature.StandaloneHealthChecks]: ["tier3", "enterprise"],
[TierFeature.AlertingRules]: ["tier3", "enterprise"],
[TierFeature.WildcardSubdomain]: ["tier1", "tier2", "tier3", "enterprise"],
[TierFeature.NewtAutoUpdate]: ["tier1", "tier2", "tier3", "enterprise"],
[TierFeature.ResourcePolicies]: ["tier3", "enterprise"],
[TierFeature.AdvancedPublicResources]: ["tier3", "enterprise"],
[TierFeature.AdvancedPrivateResources]: ["tier3", "enterprise"]
};

View File

@@ -9,10 +9,10 @@ import {
Transaction,
orgs
} from "@server/db";
import { FeatureId, getFeatureMeterId } from "./features";
import { LimitId, getFeatureMeterId } from "./features";
import logger from "@server/logger";
import { build } from "@server/build";
import cache from "#dynamic/lib/cache";
import { regionalCache as cache } from "#dynamic/lib/cache";
export function noop() {
if (build !== "saas") {
@@ -22,7 +22,6 @@ export function noop() {
}
export class UsageService {
constructor() {
if (noop()) {
return;
@@ -38,7 +37,7 @@ export class UsageService {
public async add(
orgId: string,
featureId: FeatureId,
featureId: LimitId,
value: number,
transaction: any = null
): Promise<Usage | null> {
@@ -57,7 +56,10 @@ export class UsageService {
try {
let usage;
if (transaction) {
const orgIdToUse = await this.getBillingOrg(orgId, transaction);
const orgIdToUse = await this.getBillingOrg(
orgId,
transaction
);
usage = await this.internalAddUsage(
orgIdToUse,
featureId,
@@ -112,7 +114,7 @@ export class UsageService {
private async internalAddUsage(
orgId: string, // here the orgId is the billing org already resolved by getBillingOrg in updateCount
featureId: FeatureId,
featureId: LimitId,
value: number,
trx: Transaction
): Promise<Usage> {
@@ -161,7 +163,7 @@ export class UsageService {
async updateCount(
orgId: string,
featureId: FeatureId,
featureId: LimitId,
value?: number,
customerId?: string
): Promise<void> {
@@ -225,7 +227,7 @@ export class UsageService {
private async getCustomerId(
orgId: string,
featureId: FeatureId
featureId: LimitId
): Promise<string | null> {
const orgIdToUse = await this.getBillingOrg(orgId);
@@ -267,18 +269,19 @@ export class UsageService {
public async getUsage(
orgId: string,
featureId: FeatureId,
featureId: LimitId,
trx: Transaction | typeof db = db
): Promise<Usage | null> {
if (noop()) {
return null;
}
const orgIdToUse = await this.getBillingOrg(orgId, trx);
const usageId = `${orgIdToUse}-${featureId}`;
let orgIdToUse = orgId;
try {
orgIdToUse = await this.getBillingOrg(orgId, trx);
const usageId = `${orgIdToUse}-${featureId}`;
const [result] = await trx
.select()
.from(usage)
@@ -338,8 +341,12 @@ export class UsageService {
`Failed to get usage for ${orgIdToUse}/${featureId}:`,
error
);
throw error;
if (process.env.NODE_ENV !== "development") {
throw error;
}
}
return null;
}
public async getBillingOrg(
@@ -374,7 +381,7 @@ export class UsageService {
public async checkLimitSet(
orgId: string,
featureId?: FeatureId,
featureId?: LimitId,
usage?: Usage,
trx: Transaction | typeof db = db
): Promise<boolean> {
@@ -382,13 +389,13 @@ export class UsageService {
return false;
}
const orgIdToUse = await this.getBillingOrg(orgId, trx);
// This method should check the current usage against the limits set for the organization
// and kick out all of the sites on the org
let hasExceededLimits = false;
let orgIdToUse = orgId;
try {
orgIdToUse = await this.getBillingOrg(orgId, trx);
let orgLimits: Limit[] = [];
if (featureId) {
// Get all limits set for this organization
@@ -422,7 +429,7 @@ export class UsageService {
} else {
currentUsage = await this.getUsage(
orgIdToUse,
limit.featureId as FeatureId,
limit.featureId as LimitId,
trx
);
}

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