Compare commits

..

16 Commits

Author SHA1 Message Date
dependabot[bot]
c5ca8ce0df Bump the prod-patch-updates group across 1 directory with 20 updates
Bumps the prod-patch-updates group with 20 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [@radix-ui/react-avatar](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/avatar) | `1.1.11` | `1.1.12` |
| [@radix-ui/react-checkbox](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/checkbox) | `1.3.3` | `1.3.4` |
| [@radix-ui/react-collapsible](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/collapsible) | `1.1.12` | `1.1.13` |
| [@radix-ui/react-dialog](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/dialog) | `1.1.15` | `1.1.16` |
| [@radix-ui/react-dropdown-menu](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/dropdown-menu) | `2.1.16` | `2.1.17` |
| [@radix-ui/react-label](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/label) | `2.1.8` | `2.1.9` |
| [@radix-ui/react-popover](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/popover) | `1.1.15` | `1.1.16` |
| [@radix-ui/react-progress](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/progress) | `1.1.8` | `1.1.9` |
| [@radix-ui/react-scroll-area](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/scroll-area) | `1.2.10` | `1.2.11` |
| [@radix-ui/react-separator](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/separator) | `1.1.8` | `1.1.9` |
| [@radix-ui/react-slot](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/slot) | `1.2.4` | `1.2.5` |
| [@radix-ui/react-tabs](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/tabs) | `1.1.13` | `1.1.14` |
| [@radix-ui/react-toast](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/toast) | `1.2.15` | `1.2.16` |
| [@radix-ui/react-tooltip](https://github.com/radix-ui/primitives/tree/HEAD/packages/react/tooltip) | `1.2.8` | `1.2.9` |
| [ioredis](https://github.com/luin/ioredis) | `5.11.0` | `5.11.1` |
| [next](https://github.com/vercel/next.js) | `16.2.6` | `16.2.9` |
| [react](https://github.com/facebook/react/tree/HEAD/packages/react) | `19.2.6` | `19.2.7` |
| [react-dom](https://github.com/facebook/react/tree/HEAD/packages/react-dom) | `19.2.6` | `19.2.7` |
| [semver](https://github.com/npm/node-semver) | `7.8.1` | `7.8.4` |
| [stripe](https://github.com/stripe/stripe-node) | `22.2.0` | `22.2.1` |



Updates `@radix-ui/react-avatar` from 1.1.11 to 1.1.12
- [Changelog](https://github.com/radix-ui/primitives/blob/main/packages/react/avatar/CHANGELOG.md)
- [Commits](https://github.com/radix-ui/primitives/commits/HEAD/packages/react/avatar)

Updates `@radix-ui/react-checkbox` from 1.3.3 to 1.3.4
- [Changelog](https://github.com/radix-ui/primitives/blob/main/packages/react/checkbox/CHANGELOG.md)
- [Commits](https://github.com/radix-ui/primitives/commits/HEAD/packages/react/checkbox)

Updates `@radix-ui/react-collapsible` from 1.1.12 to 1.1.13
- [Changelog](https://github.com/radix-ui/primitives/blob/main/packages/react/collapsible/CHANGELOG.md)
- [Commits](https://github.com/radix-ui/primitives/commits/HEAD/packages/react/collapsible)

Updates `@radix-ui/react-dialog` from 1.1.15 to 1.1.16
- [Changelog](https://github.com/radix-ui/primitives/blob/main/packages/react/dialog/CHANGELOG.md)
- [Commits](https://github.com/radix-ui/primitives/commits/HEAD/packages/react/dialog)

Updates `@radix-ui/react-dropdown-menu` from 2.1.16 to 2.1.17
- [Changelog](https://github.com/radix-ui/primitives/blob/main/packages/react/dropdown-menu/CHANGELOG.md)
- [Commits](https://github.com/radix-ui/primitives/commits/HEAD/packages/react/dropdown-menu)

Updates `@radix-ui/react-label` from 2.1.8 to 2.1.9
- [Changelog](https://github.com/radix-ui/primitives/blob/main/packages/react/label/CHANGELOG.md)
- [Commits](https://github.com/radix-ui/primitives/commits/HEAD/packages/react/label)

Updates `@radix-ui/react-popover` from 1.1.15 to 1.1.16
- [Changelog](https://github.com/radix-ui/primitives/blob/main/packages/react/popover/CHANGELOG.md)
- [Commits](https://github.com/radix-ui/primitives/commits/HEAD/packages/react/popover)

Updates `@radix-ui/react-progress` from 1.1.8 to 1.1.9
- [Changelog](https://github.com/radix-ui/primitives/blob/main/packages/react/progress/CHANGELOG.md)
- [Commits](https://github.com/radix-ui/primitives/commits/HEAD/packages/react/progress)

Updates `@radix-ui/react-scroll-area` from 1.2.10 to 1.2.11
- [Changelog](https://github.com/radix-ui/primitives/blob/main/packages/react/scroll-area/CHANGELOG.md)
- [Commits](https://github.com/radix-ui/primitives/commits/HEAD/packages/react/scroll-area)

Updates `@radix-ui/react-separator` from 1.1.8 to 1.1.9
- [Changelog](https://github.com/radix-ui/primitives/blob/main/packages/react/separator/CHANGELOG.md)
- [Commits](https://github.com/radix-ui/primitives/commits/HEAD/packages/react/separator)

Updates `@radix-ui/react-slot` from 1.2.4 to 1.2.5
- [Changelog](https://github.com/radix-ui/primitives/blob/main/packages/react/slot/CHANGELOG.md)
- [Commits](https://github.com/radix-ui/primitives/commits/HEAD/packages/react/slot)

Updates `@radix-ui/react-tabs` from 1.1.13 to 1.1.14
- [Changelog](https://github.com/radix-ui/primitives/blob/main/packages/react/tabs/CHANGELOG.md)
- [Commits](https://github.com/radix-ui/primitives/commits/HEAD/packages/react/tabs)

Updates `@radix-ui/react-toast` from 1.2.15 to 1.2.16
- [Changelog](https://github.com/radix-ui/primitives/blob/main/packages/react/toast/CHANGELOG.md)
- [Commits](https://github.com/radix-ui/primitives/commits/HEAD/packages/react/toast)

Updates `@radix-ui/react-tooltip` from 1.2.8 to 1.2.9
- [Changelog](https://github.com/radix-ui/primitives/blob/main/packages/react/tooltip/CHANGELOG.md)
- [Commits](https://github.com/radix-ui/primitives/commits/HEAD/packages/react/tooltip)

Updates `ioredis` from 5.11.0 to 5.11.1
- [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.11.0...v5.11.1)

Updates `next` from 16.2.6 to 16.2.9
- [Release notes](https://github.com/vercel/next.js/releases)
- [Changelog](https://github.com/vercel/next.js/blob/canary/release.js)
- [Commits](https://github.com/vercel/next.js/compare/v16.2.6...v16.2.9)

Updates `react` from 19.2.6 to 19.2.7
- [Release notes](https://github.com/facebook/react/releases)
- [Changelog](https://github.com/react/react/blob/main/CHANGELOG.md)
- [Commits](https://github.com/facebook/react/commits/v19.2.7/packages/react)

Updates `react-dom` from 19.2.6 to 19.2.7
- [Release notes](https://github.com/facebook/react/releases)
- [Changelog](https://github.com/react/react/blob/main/CHANGELOG.md)
- [Commits](https://github.com/facebook/react/commits/v19.2.7/packages/react-dom)

Updates `semver` from 7.8.1 to 7.8.4
- [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.1...v7.8.4)

Updates `stripe` from 22.2.0 to 22.2.1
- [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/v22.2.0...v22.2.1)

---
updated-dependencies:
- dependency-name: "@radix-ui/react-avatar"
  dependency-version: 1.1.12
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: prod-patch-updates
- dependency-name: "@radix-ui/react-checkbox"
  dependency-version: 1.3.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: prod-patch-updates
- dependency-name: "@radix-ui/react-collapsible"
  dependency-version: 1.1.13
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: prod-patch-updates
- dependency-name: "@radix-ui/react-dialog"
  dependency-version: 1.1.16
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: prod-patch-updates
- dependency-name: "@radix-ui/react-dropdown-menu"
  dependency-version: 2.1.17
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: prod-patch-updates
- dependency-name: "@radix-ui/react-label"
  dependency-version: 2.1.9
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: prod-patch-updates
- dependency-name: "@radix-ui/react-popover"
  dependency-version: 1.1.16
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: prod-patch-updates
- dependency-name: "@radix-ui/react-progress"
  dependency-version: 1.1.9
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: prod-patch-updates
- dependency-name: "@radix-ui/react-scroll-area"
  dependency-version: 1.2.11
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: prod-patch-updates
- dependency-name: "@radix-ui/react-separator"
  dependency-version: 1.1.9
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: prod-patch-updates
- dependency-name: "@radix-ui/react-slot"
  dependency-version: 1.2.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: prod-patch-updates
- dependency-name: "@radix-ui/react-tabs"
  dependency-version: 1.1.14
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: prod-patch-updates
- dependency-name: "@radix-ui/react-toast"
  dependency-version: 1.2.16
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: prod-patch-updates
- dependency-name: "@radix-ui/react-tooltip"
  dependency-version: 1.2.9
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: prod-patch-updates
- dependency-name: ioredis
  dependency-version: 5.11.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: prod-patch-updates
- dependency-name: next
  dependency-version: 16.2.9
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: prod-patch-updates
- dependency-name: react
  dependency-version: 19.2.7
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: prod-patch-updates
- dependency-name: react-dom
  dependency-version: 19.2.7
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: prod-patch-updates
- dependency-name: semver
  dependency-version: 7.8.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: prod-patch-updates
- dependency-name: stripe
  dependency-version: 22.2.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-06-15 01:39:33 +00: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
17 changed files with 2619 additions and 771 deletions

2878
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -43,24 +43,24 @@
"@novnc/novnc": "^1.7.0",
"@oslojs/crypto": "1.0.1",
"@oslojs/encoding": "1.1.0",
"@radix-ui/react-avatar": "1.1.11",
"@radix-ui/react-checkbox": "1.3.3",
"@radix-ui/react-collapsible": "1.1.12",
"@radix-ui/react-dialog": "1.1.15",
"@radix-ui/react-dropdown-menu": "2.1.16",
"@radix-ui/react-avatar": "1.1.12",
"@radix-ui/react-checkbox": "1.3.4",
"@radix-ui/react-collapsible": "1.1.13",
"@radix-ui/react-dialog": "1.1.16",
"@radix-ui/react-dropdown-menu": "2.1.17",
"@radix-ui/react-icons": "1.3.2",
"@radix-ui/react-label": "2.1.8",
"@radix-ui/react-popover": "1.1.15",
"@radix-ui/react-progress": "1.1.8",
"@radix-ui/react-label": "2.1.9",
"@radix-ui/react-popover": "1.1.16",
"@radix-ui/react-progress": "1.1.9",
"@radix-ui/react-radio-group": "1.3.8",
"@radix-ui/react-scroll-area": "1.2.10",
"@radix-ui/react-scroll-area": "1.2.11",
"@radix-ui/react-select": "2.2.6",
"@radix-ui/react-separator": "1.1.8",
"@radix-ui/react-slot": "1.2.4",
"@radix-ui/react-separator": "1.1.9",
"@radix-ui/react-slot": "1.2.5",
"@radix-ui/react-switch": "1.2.6",
"@radix-ui/react-tabs": "1.1.13",
"@radix-ui/react-toast": "1.2.15",
"@radix-ui/react-tooltip": "1.2.8",
"@radix-ui/react-tabs": "1.1.14",
"@radix-ui/react-toast": "1.2.16",
"@radix-ui/react-tooltip": "1.2.9",
"@react-email/body": "0.3.0",
"@react-email/components": "1.0.12",
"@react-email/render": "2.0.8",
@@ -91,14 +91,14 @@
"helmet": "8.2.0",
"http-errors": "2.0.1",
"input-otp": "1.4.2",
"ioredis": "5.11.0",
"ioredis": "5.11.1",
"jmespath": "0.16.0",
"js-yaml": "4.1.1",
"jsonwebtoken": "9.0.3",
"lucide-react": "1.17.0",
"maxmind": "5.0.6",
"moment": "2.30.1",
"next": "16.2.6",
"next": "16.2.9",
"next-intl": "4.13.0",
"next-themes": "0.4.6",
"nextjs-toploader": "3.9.17",
@@ -108,17 +108,17 @@
"pg": "8.21.0",
"posthog-node": "5.35.6",
"qrcode.react": "4.2.0",
"react": "19.2.6",
"react": "19.2.7",
"react-day-picker": "9.14.0",
"react-dom": "19.2.6",
"react-dom": "19.2.7",
"react-easy-sort": "1.8.0",
"react-hook-form": "7.76.1",
"react-icons": "5.6.0",
"recharts": "3.8.1",
"reodotdev": "1.1.0",
"semver": "7.8.1",
"semver": "7.8.4",
"sshpk": "1.18.0",
"stripe": "22.2.0",
"stripe": "22.2.1",
"swagger-ui-express": "5.0.1",
"tailwind-merge": "3.6.0",
"topojson-client": "3.1.0",
@@ -155,7 +155,7 @@
"@types/nodemailer": "8.0.0",
"@types/nprogress": "0.2.3",
"@types/pg": "8.20.0",
"@types/react": "19.2.15",
"@types/react": "19.2.17",
"@types/react-dom": "19.2.3",
"@types/semver": "7.7.1",
"@types/sshpk": "1.17.4",

View File

@@ -511,6 +511,12 @@ export class TraefikConfigManager {
let traefikConfig;
try {
const currentExitNode = await getCurrentExitNodeId();
const maintenancePort = config.getRawConfig().server.next_port;
const maintenanceHost =
config.getRawConfig().server.internal_hostname;
const pangolinUIUrl = `http://${maintenanceHost}:${maintenancePort}`;
// logger.debug(`Fetching traefik config for exit node: ${currentExitNode}`);
traefikConfig = await getTraefikConfig(
// this is called by the local exit node to get its own config
@@ -521,7 +527,8 @@ export class TraefikConfigManager {
build == "saas"
? false
: config.getRawConfig().traefik.allow_raw_resources, // dont allow raw resources on saas otherwise use config
build != "oss" // generate browser gateway targets on cloud and enterprise
pangolinUIUrl, // generate maintenance pages on cloud and hybrid
pangolinUIUrl // generate browser gateway targets on cloud and hybrid
);
const domains = new Set<string>();

View File

@@ -44,8 +44,8 @@ export async function getTraefikConfig(
filterOutNamespaceDomains = false, // UNUSED BUT USED IN PRIVATE
generateLoginPageRouters = false, // UNUSED BUT USED IN PRIVATE
allowRawResources = true,
allowMaintenancePage = true, // UNUSED BUT USED IN PRIVATE
allowBrowserGatewayResources = true
maintenancePageUiUrl: string | null = null, // UNUSED BUT USED IN PRIVATE
browserGatewayUiUrl: string | null = null // UNUSED BUT USED IN PRIVATE
): Promise<any> {
// Get resources with their targets and sites in a single optimized query
// Start from sites on this exit node, then join to targets and resources

View File

@@ -84,8 +84,8 @@ export async function getTraefikConfig(
filterOutNamespaceDomains = false,
generateLoginPageRouters = false,
allowRawResources = true,
allowMaintenancePage = true,
allowBrowserGatewayResources = true
maintenancePageUiUrl: string | null = null,
browserGatewayUiUrl: string | null = null
): Promise<any> {
// Get resources with their targets and sites in a single optimized query
// Start from sites on this exit node, then join to targets and resources
@@ -317,7 +317,7 @@ export async function getTraefikConfig(
BrowserGatewayResourceEntry
>();
if (allowBrowserGatewayResources) {
if (browserGatewayUiUrl) {
for (const row of resourcesWithTargetsAndSites) {
if (!["ssh", "vnc", "rdp"].includes(row.mode)) {
continue;
@@ -630,10 +630,11 @@ export async function getTraefikConfig(
}
}
if (showMaintenancePage && allowMaintenancePage) {
if (showMaintenancePage && maintenancePageUiUrl) {
const maintenanceServiceName = `${key}-maintenance-service`;
const maintenanceRouterName = `${key}-maintenance-router`;
const rewriteMiddlewareName = `${key}-maintenance-rewrite`;
const maintenanceHeadersMiddlewareName = `${key}-maintenance-headers`;
const entrypointHttp =
config.getRawConfig().traefik.http_entrypoint;
@@ -646,15 +647,11 @@ export async function getTraefikConfig(
? `*.${domainParts.slice(1).join(".")}`
: fullDomain;
const maintenancePort = config.getRawConfig().server.next_port;
const maintenanceHost =
config.getRawConfig().server.internal_hostname;
config_output.http.services[maintenanceServiceName] = {
loadBalancer: {
servers: [
{
url: `http://${maintenanceHost}:${maintenancePort}`
url: maintenancePageUiUrl
}
],
passHostHeader: true
@@ -673,12 +670,26 @@ export async function getTraefikConfig(
}
};
config_output.http.middlewares[
maintenanceHeadersMiddlewareName
] = {
headers: {
customRequestHeaders: {
Host: "app.pangolin.net", // if we are sending to the cloud the host needs to be this but we will pull the p-host to find the resource
"p-host": fullDomain
}
}
};
config_output.http.routers[maintenanceRouterName] = {
entryPoints: [
resource.ssl ? entrypointHttps : entrypointHttp
],
service: maintenanceServiceName,
middlewares: [rewriteMiddlewareName],
middlewares: [
rewriteMiddlewareName,
maintenanceHeadersMiddlewareName
],
rule: rule,
priority: 2000,
...(resource.ssl ? { tls } : {})
@@ -691,6 +702,7 @@ export async function getTraefikConfig(
resource.ssl ? entrypointHttps : entrypointHttp
],
service: maintenanceServiceName,
middlewares: [maintenanceHeadersMiddlewareName],
rule: `${rule} && (PathPrefix(\`/_next\`) || PathRegexp(\`^/__nextjs*\`) || Path(\`/favicon.ico\`)) `,
priority: 2001,
...(resource.ssl ? { tls } : {})
@@ -1027,7 +1039,7 @@ export async function getTraefikConfig(
}
}
if (allowBrowserGatewayResources) {
if (browserGatewayUiUrl) {
// Generate Traefik config for browser gateway resources
const browserGatewayPort = 39999;
for (const [, bgResource] of browserGatewayResourcesMap.entries()) {
@@ -1119,20 +1131,17 @@ export async function getTraefikConfig(
}
}
if (showBgMaintenancePage && allowMaintenancePage) {
if (showBgMaintenancePage && maintenancePageUiUrl) {
const bgMaintenanceServiceName = `bg-r${bgResource.resourceId}-maintenance-service`;
const bgMaintenanceRouterName = `bg-r${bgResource.resourceId}-maintenance-router`;
const bgRewriteMiddlewareName = `bg-r${bgResource.resourceId}-maintenance-rewrite`;
const bgMaintenanceHeadersMiddlewareName = `bg-r${bgResource.resourceId}-maintenance-headers`;
const entrypointHttp =
config.getRawConfig().traefik.http_entrypoint;
const entrypointHttps =
config.getRawConfig().traefik.https_entrypoint;
const maintenancePort = config.getRawConfig().server.next_port;
const maintenanceHost =
config.getRawConfig().server.internal_hostname;
if (!config_output.http.services)
config_output.http.services = {};
if (!config_output.http.middlewares)
@@ -1144,7 +1153,7 @@ export async function getTraefikConfig(
loadBalancer: {
servers: [
{
url: `http://${maintenanceHost}:${maintenancePort}`
url: maintenancePageUiUrl
}
],
passHostHeader: true
@@ -1158,12 +1167,26 @@ export async function getTraefikConfig(
}
};
config_output.http.middlewares![
bgMaintenanceHeadersMiddlewareName
] = {
headers: {
customRequestHeaders: {
Host: "app.pangolin.net", // if we are sending to the cloud the host needs to be this but we will pull the p-host to find the resource
"p-host": fullDomain
}
}
};
config_output.http.routers![bgMaintenanceRouterName] = {
entryPoints: [
bgResource.ssl ? entrypointHttps : entrypointHttp
],
service: bgMaintenanceServiceName,
middlewares: [bgRewriteMiddlewareName],
middlewares: [
bgRewriteMiddlewareName,
bgMaintenanceHeadersMiddlewareName
],
rule: hostRule,
priority: 2000,
...(bgResource.ssl ? { tls } : {})
@@ -1176,6 +1199,7 @@ export async function getTraefikConfig(
bgResource.ssl ? entrypointHttps : entrypointHttp
],
service: bgMaintenanceServiceName,
middlewares: [bgMaintenanceHeadersMiddlewareName],
rule: `${hostRule} && (PathPrefix(\`/_next\`) || PathRegexp(\`^/__nextjs*\`) || Path(\`/favicon.ico\`))`,
priority: 2001,
...(bgResource.ssl ? { tls } : {})
@@ -1234,9 +1258,8 @@ export async function getTraefikConfig(
// The primary type is used for the path rewrite (e.g. /rdp), mirroring
// how the maintenance page rewrites everything to /maintenance-screen.
const primaryType = typeMap.keys().next().value as string;
const internalHost = config.getRawConfig().server.internal_hostname;
const internalPort = config.getRawConfig().server.next_port;
const uiRewriteMiddlewareName = `bg-r${bgResource.resourceId}-ui-rewrite`;
const uiHeadersMiddlewareName = `bg-r${bgResource.resourceId}-ui-headers`;
const entrypoint = bgResource.ssl
? config.getRawConfig().traefik.https_entrypoint
: config.getRawConfig().traefik.http_entrypoint;
@@ -1252,22 +1275,33 @@ export async function getTraefikConfig(
}
};
config_output.http.middlewares![uiHeadersMiddlewareName] = {
headers: {
customRequestHeaders: {
Host: "app.pangolin.net", // if we are sending to the cloud the host needs to be this but we will pull the p-host to find the resource
"p-host": fullDomain
}
}
};
config_output.http.services![bgUiServiceName] = {
loadBalancer: {
servers: [
{
url: `http://${internalHost}:${internalPort}`
url: browserGatewayUiUrl
}
]
}
};
// Assets router at higher priority so /_next files load without rewrite
// Assets router at higher priority so /_next files load without rewrite.
// Do NOT apply the path-rewrite middleware here — static assets must
// keep their original path; only the host headers are needed.
config_output.http.routers![
`bg-r${bgResource.resourceId}-assets-router`
] = {
entryPoints: [entrypoint],
middlewares: routerMiddlewares,
middlewares: [...routerMiddlewares, uiHeadersMiddlewareName],
service: bgUiServiceName,
rule: `${hostRule} && (PathPrefix(\`/_next\`) || PathRegexp(\`^/__nextjs*\`) || Path(\`/favicon.ico\`))`,
priority: 101,
@@ -1279,7 +1313,11 @@ export async function getTraefikConfig(
`bg-r${bgResource.resourceId}-ui-router`
] = {
entryPoints: [entrypoint],
middlewares: [...routerMiddlewares, uiRewriteMiddlewareName],
middlewares: [
...routerMiddlewares,
uiRewriteMiddlewareName,
uiHeadersMiddlewareName
],
service: bgUiServiceName,
rule: hostRule,
priority: 100,
@@ -1312,10 +1350,6 @@ export async function getTraefikConfig(
const siteResourceRouterName = `${srKey}-router`;
const siteResourceRewriteMiddlewareName = `${srKey}-rewrite`;
const maintenancePort = config.getRawConfig().server.next_port;
const maintenanceHost =
config.getRawConfig().server.internal_hostname;
if (!config_output.http.routers) {
config_output.http.routers = {};
}
@@ -1331,7 +1365,7 @@ export async function getTraefikConfig(
loadBalancer: {
servers: [
{
url: `http://${maintenanceHost}:${maintenancePort}`
url: maintenancePageUiUrl
}
],
passHostHeader: true

View File

@@ -277,6 +277,8 @@ hybridRouter.get(
);
}
const pangolinUIUrl = config.getRawConfig().app.dashboard_url; // points to the dashboard to serve from there
try {
const traefikConfig = await getTraefikConfig(
remoteExitNode.exitNodeId,
@@ -284,8 +286,8 @@ hybridRouter.get(
true, // But don't allow domain namespace resources
false, // Dont include login pages,
true, // allow raw resources
false, // dont generate maintenance page
false // dont generate browser gateway targets
pangolinUIUrl, // dont generate maintenance page
pangolinUIUrl // generate browser gateway targets
);
return response(res, {

View File

@@ -54,7 +54,7 @@ export const handleNewtGetConfigMessage: MessageHandler = async (context) => {
// TODO: somehow we should make sure a recent hole punch has happened if this occurs (hole punch could be from the last restart if done quickly)
}
if (existingSite.lastHolePunch && now - existingSite.lastHolePunch > 5) {
if (existingSite.lastHolePunch && now - existingSite.lastHolePunch > 12) {
logger.warn(
`Site last hole punch is too old; skipping this register. The site is failing to hole punch and identify its network address with the server. Can the site reach the server on UDP port ${config.getRawConfig().gerbil.clients_start_port}?`
);

View File

@@ -348,7 +348,7 @@ export const handleOlmRegisterMessage: MessageHandler = async (context) => {
// this prevents us from accepting a register from an olm that has not hole punched yet.
// the olm will pump the register so we can keep checking
// TODO: I still think there is a better way to do this rather than locking it out here but ???
if (now - (client.lastHolePunch || 0) > 5 && sitesCount > 0) {
if (now - (client.lastHolePunch || 0) > 12 && sitesCount > 0) {
logger.warn(
`[handleOlmRegisterMessage] Client last hole punch is too old and we have sites to send; skipping this register. The client is failing to hole punch and identify its network address with the server. Can the client reach the server on UDP port ${config.getRawConfig().gerbil.clients_start_port}?`,
{ orgId: client.orgId, clientId: client.clientId }

View File

@@ -3,7 +3,6 @@ import HttpCode from "@server/types/HttpCode";
import createHttpError from "http-errors";
import logger from "@server/logger";
import { response as sendResponse } from "@server/lib/response";
import config from "@server/lib/config";
import { build } from "@server/build";
import { APP_VERSION } from "@server/lib/consts";
import license from "#dynamic/license/license";
@@ -22,9 +21,6 @@ export async function getServerInfo(
next: NextFunction
): Promise<any> {
try {
const supporterData = config.getSupporterData();
const supporterStatusValid = supporterData?.valid || false;
let enterpriseLicenseValid = false;
let enterpriseLicenseType: string | null = null;
@@ -41,7 +37,7 @@ export async function getServerInfo(
return sendResponse<GetServerInfoResponse>(res, {
data: {
version: APP_VERSION,
supporterStatusValid,
supporterStatusValid: true,
build,
enterpriseLicenseValid,
enterpriseLicenseType

View File

@@ -17,13 +17,18 @@ export async function traefikConfigProvider(
// Get the current exit node name from config
const currentExitNodeId = await getCurrentExitNodeId();
const maintenancePort = config.getRawConfig().server.next_port;
const maintenanceHost = config.getRawConfig().server.internal_hostname;
const pangolinUIUrl = `http://${maintenanceHost}:${maintenancePort}`;
const traefikConfig = await getTraefikConfig(
currentExitNodeId,
config.getRawConfig().traefik.site_types,
build == "oss", // filter out the namespace domains in open source
build != "oss", // generate the login pages on the cloud and and enterprise,
config.getRawConfig().traefik.allow_raw_resources,
build != "oss" // generate browser gateway resources on cloud and enterprise
pangolinUIUrl,
pangolinUIUrl
);
if (traefikConfig?.http?.middlewares) {

View File

@@ -42,7 +42,14 @@ import {
SettingsSectionFooter
} from "@app/components/Settings";
import SettingsSectionTitle from "@app/components/SettingsSectionTitle";
import { ArrowRight, Check, ExternalLink, Heart, InfoIcon, TicketCheck } from "lucide-react";
import {
ArrowRight,
Check,
ExternalLink,
Heart,
InfoIcon,
TicketCheck
} from "lucide-react";
import Link from "next/link";
import DismissableBanner from "@app/components/DismissableBanner";
import CopyTextBox from "@app/components/CopyTextBox";
@@ -50,7 +57,7 @@ import ConfirmDeleteDialog from "@app/components/ConfirmDeleteDialog";
import { SitePriceCalculator } from "@app/components/SitePriceCalculator";
import { Checkbox } from "@app/components/ui/checkbox";
import { Alert, AlertDescription, AlertTitle } from "@app/components/ui/alert";
import { useSupporterStatusContext } from "@app/hooks/useSupporterStatusContext";
// import { useSupporterStatusContext } from "@app/hooks/useSupporterStatusContext";
import { useTranslations } from "next-intl";
const ENTERPRISE_DOCS_URL =
@@ -82,7 +89,7 @@ export default function LicensePage() {
const [isActivatingLicense, setIsActivatingLicense] = useState(false);
const [isDeletingLicense, setIsDeletingLicense] = useState(false);
const [isRecheckingLicense, setIsRecheckingLicense] = useState(false);
const { supporterStatus } = useSupporterStatusContext();
// const { supporterStatus } = useSupporterStatusContext();
const t = useTranslations();
@@ -347,9 +354,7 @@ export default function LicensePage() {
storageKey="license-banner-dismissed"
version={1}
title={t("licenseBannerTitle")}
titleIcon={
<TicketCheck className="w-5 h-5 text-primary" />
}
titleIcon={<TicketCheck className="w-5 h-5 text-primary" />}
description={t("licenseBannerDescription")}
>
<Link

View File

@@ -68,15 +68,15 @@ export default async function RootLayout({
const env = pullEnv();
const locale = await getLocale();
const supporterData = {
visible: true
} as any;
// const supporterData = {
// visible: true
// } as any;
const res = await priv.get<AxiosResponse<IsSupporterKeyVisibleResponse>>(
"supporter-key/visible"
);
supporterData.visible = res.data.data.visible;
supporterData.tier = res.data.data.tier;
// const res = await priv.get<AxiosResponse<IsSupporterKeyVisibleResponse>>(
// "supporter-key/visible"
// );
// supporterData.visible = res.data.data.visible;
// supporterData.tier = res.data.data.tier;
let licenseStatus: GetLicenseStatusResponse;
if (build === "enterprise") {
@@ -127,20 +127,20 @@ export default async function RootLayout({
<LicenseStatusProvider
licenseStatus={licenseStatus}
>
<SupportStatusProvider
{/* <SupportStatusProvider
supporterStatus={supporterData}
>
{/* Main content */}
<div className="h-full flex flex-col">
<div className="flex-1 overflow-auto">
<SplashImage>
<LicenseViolation />
{children}
</SplashImage>
> */}
{/* Main content */}
<div className="h-full flex flex-col">
<div className="flex-1 overflow-auto">
<SplashImage>
<LicenseViolation />
</div>
{children}
</SplashImage>
<LicenseViolation />
</div>
</SupportStatusProvider>
</div>
{/* </SupportStatusProvider> */}
</LicenseStatusProvider>
<Toaster />
</TanstackQueryProvider>

View File

@@ -28,7 +28,7 @@ export default async function MaintenanceScreen() {
try {
const headersList = await headers();
const host = headersList.get("host") || "";
const host = headersList.get("p-host") || headersList.get("host") || "";
const hostname = host.split(":")[0];
const res = await priv.get<AxiosResponse<GetMaintenanceInfoResponse>>(

View File

@@ -1,24 +1,24 @@
"use client";
import { useSupporterStatusContext } from "@app/hooks/useSupporterStatusContext";
// import { useSupporterStatusContext } from "@app/hooks/useSupporterStatusContext";
import { useLicenseStatusContext } from "@app/hooks/useLicenseStatusContext";
import { useTranslations } from "next-intl";
import { build } from "@server/build";
export default function AuthPageFooterNotices() {
const t = useTranslations();
const { supporterStatus } = useSupporterStatusContext();
// const { supporterStatus } = useSupporterStatusContext();
const { isUnlocked, licenseStatus } = useLicenseStatusContext();
return (
<>
{supporterStatus?.visible && (
{/* {supporterStatus?.visible && (
<div className="text-center mt-2">
<span className="text-sm text-muted-foreground opacity-50">
{t("noSupportKey")}
</span>
</div>
)}
)} */}
{build === "enterprise" && !isUnlocked() ? (
<div className="text-center mt-2">
<span className="text-sm font-medium text-muted-foreground">

View File

@@ -9,33 +9,34 @@ export default function SupporterMessage({ tier }: { tier: string }) {
const t = useTranslations();
return (
<div className="relative flex items-center space-x-2 whitespace-nowrap group">
<span
className="cursor-pointer"
onClick={(e) => {
// Get the bounding box of the element
const rect = (
e.target as HTMLElement
).getBoundingClientRect();
<></>
// <div className="relative flex items-center space-x-2 whitespace-nowrap group">
// <span
// className="cursor-pointer"
// onClick={(e) => {
// // Get the bounding box of the element
// const rect = (
// e.target as HTMLElement
// ).getBoundingClientRect();
// Trigger confetti centered on the word "Pangolin"
confetti({
particleCount: 100,
spread: 70,
origin: {
x: (rect.left + rect.width / 2) / window.innerWidth,
y: rect.top / window.innerHeight
},
colors: ["#FFA500", "#FF4500", "#FFD700"]
});
}}
>
Pangolin
</span>
<Star className="w-3 h-3" />
<div className="absolute left-1/2 transform -translate-x-1/2 -top-10 hidden group-hover:block text-primary text-sm rounded-md border shadow-md px-4 py-2 pointer-events-none opacity-0 group-hover:opacity-100 transition-opacity">
{t("componentsSupporterMessage", { tier: tier })}
</div>
</div>
// // Trigger confetti centered on the word "Pangolin"
// confetti({
// particleCount: 100,
// spread: 70,
// origin: {
// x: (rect.left + rect.width / 2) / window.innerWidth,
// y: rect.top / window.innerHeight
// },
// colors: ["#FFA500", "#FF4500", "#FFD700"]
// });
// }}
// >
// Pangolin
// </span>
// <Star className="w-3 h-3" />
// <div className="absolute left-1/2 transform -translate-x-1/2 -top-10 hidden group-hover:block text-primary text-sm rounded-md border shadow-md px-4 py-2 pointer-events-none opacity-0 group-hover:opacity-100 transition-opacity">
// {t("componentsSupporterMessage", { tier: tier })}
// </div>
// </div>
);
}

View File

@@ -3,7 +3,7 @@
// THIS IS DEPRECATED AND IS NO LONGER SHOWED TO THE USER WITH THE DISCONTINUATION
// OF THE SUPPORTER PROGRAM. IT MAY BE REMOVED IN A FUTURE UPDATE.
import { useSupporterStatusContext } from "@app/hooks/useSupporterStatusContext";
// import { useSupporterStatusContext } from "@app/hooks/useSupporterStatusContext";
import { useState, useTransition } from "react";
import {
Tooltip,
@@ -58,134 +58,134 @@ interface SupporterStatusProps {
export default function SupporterStatus({
isCollapsed = false
}: SupporterStatusProps) {
const { supporterStatus, updateSupporterStatus } =
useSupporterStatusContext();
const [supportOpen, setSupportOpen] = useState(false);
const [keyOpen, setKeyOpen] = useState(false);
const [purchaseOptionsOpen, setPurchaseOptionsOpen] = useState(false);
// const { supporterStatus, updateSupporterStatus } =
// useSupporterStatusContext();
// const [supportOpen, setSupportOpen] = useState(false);
// const [keyOpen, setKeyOpen] = useState(false);
// const [purchaseOptionsOpen, setPurchaseOptionsOpen] = useState(false);
const { env } = useEnvContext();
const api = createApiClient({ env });
const t = useTranslations();
// const { env } = useEnvContext();
// const api = createApiClient({ env });
// const t = useTranslations();
const formSchema = z.object({
githubUsername: z.string().nonempty({
error: "GitHub username is required"
}),
key: z.string().nonempty({
error: "Supporter key is required"
})
});
// const formSchema = z.object({
// githubUsername: z.string().nonempty({
// error: "GitHub username is required"
// }),
// key: z.string().nonempty({
// error: "Supporter key is required"
// })
// });
const form = useForm({
resolver: zodResolver(formSchema),
defaultValues: {
githubUsername: "",
key: ""
}
});
// const form = useForm({
// resolver: zodResolver(formSchema),
// defaultValues: {
// githubUsername: "",
// key: ""
// }
// });
async function hide() {
await api.post("/supporter-key/hide");
// async function hide() {
// await api.post("/supporter-key/hide");
updateSupporterStatus({
visible: false
});
}
// updateSupporterStatus({
// visible: false
// });
// }
async function onSubmit(values: z.infer<typeof formSchema>) {
try {
const res = await api.post<
AxiosResponse<ValidateSupporterKeyResponse>
>("/supporter-key/validate", {
githubUsername: values.githubUsername,
key: values.key
});
// async function onSubmit(values: z.infer<typeof formSchema>) {
// try {
// const res = await api.post<
// AxiosResponse<ValidateSupporterKeyResponse>
// >("/supporter-key/validate", {
// githubUsername: values.githubUsername,
// key: values.key
// });
const data = res.data.data;
// const data = res.data.data;
if (!data || !data.valid) {
toast({
variant: "destructive",
title: t("supportKeyInvalid"),
description: t("supportKeyInvalidDescription")
});
return;
}
// if (!data || !data.valid) {
// toast({
// variant: "destructive",
// title: t("supportKeyInvalid"),
// description: t("supportKeyInvalidDescription")
// });
// return;
// }
// Trigger the toast
toast({
variant: "default",
title: t("supportKeyValid"),
description: t("supportKeyValidDescription")
});
// // Trigger the toast
// toast({
// variant: "default",
// title: t("supportKeyValid"),
// description: t("supportKeyValidDescription")
// });
// Fireworks-style confetti
const duration = 5 * 1000; // 5 seconds
const animationEnd = Date.now() + duration;
const defaults = {
startVelocity: 30,
spread: 360,
ticks: 60,
zIndex: 0,
colors: ["#FFA500", "#FF4500", "#FFD700"] // Orange hues
};
// // Fireworks-style confetti
// const duration = 5 * 1000; // 5 seconds
// const animationEnd = Date.now() + duration;
// const defaults = {
// startVelocity: 30,
// spread: 360,
// ticks: 60,
// zIndex: 0,
// colors: ["#FFA500", "#FF4500", "#FFD700"] // Orange hues
// };
function randomInRange(min: number, max: number) {
return Math.random() * (max - min) + min;
}
// function randomInRange(min: number, max: number) {
// return Math.random() * (max - min) + min;
// }
const interval = setInterval(() => {
const timeLeft = animationEnd - Date.now();
// const interval = setInterval(() => {
// const timeLeft = animationEnd - Date.now();
if (timeLeft <= 0) {
clearInterval(interval);
return;
}
// if (timeLeft <= 0) {
// clearInterval(interval);
// return;
// }
const particleCount = 50 * (timeLeft / duration);
// const particleCount = 50 * (timeLeft / duration);
// Launch confetti from two random horizontal positions
confetti({
...defaults,
particleCount,
origin: {
x: randomInRange(0.1, 0.3),
y: Math.random() - 0.2
}
});
confetti({
...defaults,
particleCount,
origin: {
x: randomInRange(0.7, 0.9),
y: Math.random() - 0.2
}
});
}, 250);
// // Launch confetti from two random horizontal positions
// confetti({
// ...defaults,
// particleCount,
// origin: {
// x: randomInRange(0.1, 0.3),
// y: Math.random() - 0.2
// }
// });
// confetti({
// ...defaults,
// particleCount,
// origin: {
// x: randomInRange(0.7, 0.9),
// y: Math.random() - 0.2
// }
// });
// }, 250);
setPurchaseOptionsOpen(false);
setKeyOpen(false);
// setPurchaseOptionsOpen(false);
// setKeyOpen(false);
updateSupporterStatus({
visible: false
});
} catch (error) {
toast({
variant: "destructive",
title: t("error"),
description: formatAxiosError(
error,
t("supportKeyErrorValidationDescription")
)
});
return;
}
}
// updateSupporterStatus({
// visible: false
// });
// } catch (error) {
// toast({
// variant: "destructive",
// title: t("error"),
// description: formatAxiosError(
// error,
// t("supportKeyErrorValidationDescription")
// )
// });
// return;
// }
// }
return (
<>
<Credenza
{/* <Credenza
open={purchaseOptionsOpen}
onOpenChange={(val) => {
setPurchaseOptionsOpen(val);
@@ -469,7 +469,7 @@ export default function SupporterStatus({
{t("supportKeyBuy")}
</Button>
)
) : null}
) : null} */}
</>
);
}

View File

@@ -6,7 +6,7 @@ import { cache } from "react";
export const getBrowserTargetForRequest = cache(async () => {
const headersList = await headers();
const host = headersList.get("host") || "";
const host = headersList.get("p-host") || headersList.get("host") || "";
const hostname = host.split(":")[0];
try {