Compare commits

..

4 Commits
dev ... 1.19.2

Author SHA1 Message Date
Owen Schwartz
b9db0a4490 Merge pull request #3261 from fosrl/dev
1.19.2
2026-06-12 15:02:58 -07:00
Owen Schwartz
d9952b0762 Merge pull request #3250 from fosrl/dev
1.19.1
2026-06-11 22:05:24 -07:00
Owen Schwartz
6e271028f3 Merge pull request #3245 from fosrl/dev
Bugfixes
2026-06-11 16:17:41 -07:00
Owen Schwartz
a724b07846 Merge pull request #3244 from fosrl/dev
fix paywalling
2026-06-11 12:27:49 -07:00
16 changed files with 211 additions and 260 deletions

View File

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

View File

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

View File

@@ -277,8 +277,6 @@ hybridRouter.get(
); );
} }
const pangolinUIUrl = config.getRawConfig().app.dashboard_url; // points to the dashboard to serve from there
try { try {
const traefikConfig = await getTraefikConfig( const traefikConfig = await getTraefikConfig(
remoteExitNode.exitNodeId, remoteExitNode.exitNodeId,
@@ -286,8 +284,8 @@ hybridRouter.get(
true, // But don't allow domain namespace resources true, // But don't allow domain namespace resources
false, // Dont include login pages, false, // Dont include login pages,
true, // allow raw resources true, // allow raw resources
pangolinUIUrl, // dont generate maintenance page false, // dont generate maintenance page
pangolinUIUrl // generate browser gateway targets false // dont generate browser gateway targets
); );
return response(res, { 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) // 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 > 12) { if (existingSite.lastHolePunch && now - existingSite.lastHolePunch > 5) {
logger.warn( 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}?` `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. // 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 // 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 ??? // TODO: I still think there is a better way to do this rather than locking it out here but ???
if (now - (client.lastHolePunch || 0) > 12 && sitesCount > 0) { if (now - (client.lastHolePunch || 0) > 5 && sitesCount > 0) {
logger.warn( 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}?`, `[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 } { orgId: client.orgId, clientId: client.clientId }

View File

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

View File

@@ -17,18 +17,13 @@ export async function traefikConfigProvider(
// Get the current exit node name from config // Get the current exit node name from config
const currentExitNodeId = await getCurrentExitNodeId(); 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( const traefikConfig = await getTraefikConfig(
currentExitNodeId, currentExitNodeId,
config.getRawConfig().traefik.site_types, config.getRawConfig().traefik.site_types,
build == "oss", // filter out the namespace domains in open source build == "oss", // filter out the namespace domains in open source
build != "oss", // generate the login pages on the cloud and and enterprise, build != "oss", // generate the login pages on the cloud and and enterprise,
config.getRawConfig().traefik.allow_raw_resources, config.getRawConfig().traefik.allow_raw_resources,
pangolinUIUrl, build != "oss" // generate browser gateway resources on cloud and enterprise
pangolinUIUrl
); );
if (traefikConfig?.http?.middlewares) { if (traefikConfig?.http?.middlewares) {

View File

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

View File

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

View File

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

View File

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

View File

@@ -9,34 +9,33 @@ export default function SupporterMessage({ tier }: { tier: string }) {
const t = useTranslations(); const t = useTranslations();
return ( return (
<></> <div className="relative flex items-center space-x-2 whitespace-nowrap group">
// <div className="relative flex items-center space-x-2 whitespace-nowrap group"> <span
// <span className="cursor-pointer"
// className="cursor-pointer" onClick={(e) => {
// onClick={(e) => { // Get the bounding box of the element
// // Get the bounding box of the element const rect = (
// const rect = ( e.target as HTMLElement
// e.target as HTMLElement ).getBoundingClientRect();
// ).getBoundingClientRect();
// // Trigger confetti centered on the word "Pangolin" // Trigger confetti centered on the word "Pangolin"
// confetti({ confetti({
// particleCount: 100, particleCount: 100,
// spread: 70, spread: 70,
// origin: { origin: {
// x: (rect.left + rect.width / 2) / window.innerWidth, x: (rect.left + rect.width / 2) / window.innerWidth,
// y: rect.top / window.innerHeight y: rect.top / window.innerHeight
// }, },
// colors: ["#FFA500", "#FF4500", "#FFD700"] colors: ["#FFA500", "#FF4500", "#FFD700"]
// }); });
// }} }}
// > >
// Pangolin Pangolin
// </span> </span>
// <Star className="w-3 h-3" /> <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"> <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 })} {t("componentsSupporterMessage", { tier: tier })}
// </div> </div>
// </div> </div>
); );
} }

View File

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

View File

@@ -139,6 +139,7 @@ Restart=always
RestartSec=2 RestartSec=2
UMask=0077 UMask=0077
NoNewPrivileges=true
PrivateTmp=true PrivateTmp=true
[Install] [Install]

View File

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