mirror of
https://github.com/fosrl/pangolin.git
synced 2026-06-17 21:01:53 +00:00
Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f9cc52ece9 | ||
|
|
a08c6d70fe | ||
|
|
a6568692b7 | ||
|
|
a1196d3da6 | ||
|
|
70bc4c0b30 | ||
|
|
ea1badf4e0 | ||
|
|
4435a669a6 | ||
|
|
90eceb457a | ||
|
|
f39cbc9bf4 | ||
|
|
50da863bb7 | ||
|
|
c6ddd5c402 | ||
|
|
0fb5ace9c7 |
@@ -511,6 +511,12 @@ 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
|
||||||
@@ -521,7 +527,8 @@ 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
|
||||||
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>();
|
const domains = new Set<string>();
|
||||||
|
|||||||
@@ -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,
|
||||||
allowMaintenancePage = true, // UNUSED BUT USED IN PRIVATE
|
maintenancePageUiUrl: string | null = null, // UNUSED BUT USED IN PRIVATE
|
||||||
allowBrowserGatewayResources = true
|
browserGatewayUiUrl: string | null = null // UNUSED BUT USED IN PRIVATE
|
||||||
): 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
|
||||||
|
|||||||
@@ -84,8 +84,8 @@ export async function getTraefikConfig(
|
|||||||
filterOutNamespaceDomains = false,
|
filterOutNamespaceDomains = false,
|
||||||
generateLoginPageRouters = false,
|
generateLoginPageRouters = false,
|
||||||
allowRawResources = true,
|
allowRawResources = true,
|
||||||
allowMaintenancePage = true,
|
maintenancePageUiUrl: string | null = null,
|
||||||
allowBrowserGatewayResources = true
|
browserGatewayUiUrl: string | null = null
|
||||||
): 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 (allowBrowserGatewayResources) {
|
if (browserGatewayUiUrl) {
|
||||||
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,10 +630,11 @@ export async function getTraefikConfig(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (showMaintenancePage && allowMaintenancePage) {
|
if (showMaintenancePage && maintenancePageUiUrl) {
|
||||||
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;
|
||||||
@@ -646,15 +647,11 @@ 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: `http://${maintenanceHost}:${maintenancePort}`
|
url: maintenancePageUiUrl
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
passHostHeader: true
|
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] = {
|
config_output.http.routers[maintenanceRouterName] = {
|
||||||
entryPoints: [
|
entryPoints: [
|
||||||
resource.ssl ? entrypointHttps : entrypointHttp
|
resource.ssl ? entrypointHttps : entrypointHttp
|
||||||
],
|
],
|
||||||
service: maintenanceServiceName,
|
service: maintenanceServiceName,
|
||||||
middlewares: [rewriteMiddlewareName],
|
middlewares: [
|
||||||
|
rewriteMiddlewareName,
|
||||||
|
maintenanceHeadersMiddlewareName
|
||||||
|
],
|
||||||
rule: rule,
|
rule: rule,
|
||||||
priority: 2000,
|
priority: 2000,
|
||||||
...(resource.ssl ? { tls } : {})
|
...(resource.ssl ? { tls } : {})
|
||||||
@@ -691,6 +702,7 @@ 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 } : {})
|
||||||
@@ -1027,7 +1039,7 @@ export async function getTraefikConfig(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (allowBrowserGatewayResources) {
|
if (browserGatewayUiUrl) {
|
||||||
// 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()) {
|
||||||
@@ -1119,20 +1131,17 @@ export async function getTraefikConfig(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (showBgMaintenancePage && allowMaintenancePage) {
|
if (showBgMaintenancePage && maintenancePageUiUrl) {
|
||||||
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)
|
||||||
@@ -1144,7 +1153,7 @@ export async function getTraefikConfig(
|
|||||||
loadBalancer: {
|
loadBalancer: {
|
||||||
servers: [
|
servers: [
|
||||||
{
|
{
|
||||||
url: `http://${maintenanceHost}:${maintenancePort}`
|
url: maintenancePageUiUrl
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
passHostHeader: true
|
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] = {
|
config_output.http.routers![bgMaintenanceRouterName] = {
|
||||||
entryPoints: [
|
entryPoints: [
|
||||||
bgResource.ssl ? entrypointHttps : entrypointHttp
|
bgResource.ssl ? entrypointHttps : entrypointHttp
|
||||||
],
|
],
|
||||||
service: bgMaintenanceServiceName,
|
service: bgMaintenanceServiceName,
|
||||||
middlewares: [bgRewriteMiddlewareName],
|
middlewares: [
|
||||||
|
bgRewriteMiddlewareName,
|
||||||
|
bgMaintenanceHeadersMiddlewareName
|
||||||
|
],
|
||||||
rule: hostRule,
|
rule: hostRule,
|
||||||
priority: 2000,
|
priority: 2000,
|
||||||
...(bgResource.ssl ? { tls } : {})
|
...(bgResource.ssl ? { tls } : {})
|
||||||
@@ -1176,6 +1199,7 @@ 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 } : {})
|
||||||
@@ -1234,9 +1258,8 @@ 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;
|
||||||
@@ -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] = {
|
config_output.http.services![bgUiServiceName] = {
|
||||||
loadBalancer: {
|
loadBalancer: {
|
||||||
servers: [
|
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![
|
config_output.http.routers![
|
||||||
`bg-r${bgResource.resourceId}-assets-router`
|
`bg-r${bgResource.resourceId}-assets-router`
|
||||||
] = {
|
] = {
|
||||||
entryPoints: [entrypoint],
|
entryPoints: [entrypoint],
|
||||||
middlewares: routerMiddlewares,
|
middlewares: [...routerMiddlewares, uiHeadersMiddlewareName],
|
||||||
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,
|
||||||
@@ -1279,7 +1313,11 @@ export async function getTraefikConfig(
|
|||||||
`bg-r${bgResource.resourceId}-ui-router`
|
`bg-r${bgResource.resourceId}-ui-router`
|
||||||
] = {
|
] = {
|
||||||
entryPoints: [entrypoint],
|
entryPoints: [entrypoint],
|
||||||
middlewares: [...routerMiddlewares, uiRewriteMiddlewareName],
|
middlewares: [
|
||||||
|
...routerMiddlewares,
|
||||||
|
uiRewriteMiddlewareName,
|
||||||
|
uiHeadersMiddlewareName
|
||||||
|
],
|
||||||
service: bgUiServiceName,
|
service: bgUiServiceName,
|
||||||
rule: hostRule,
|
rule: hostRule,
|
||||||
priority: 100,
|
priority: 100,
|
||||||
@@ -1312,10 +1350,6 @@ 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 = {};
|
||||||
}
|
}
|
||||||
@@ -1331,7 +1365,7 @@ export async function getTraefikConfig(
|
|||||||
loadBalancer: {
|
loadBalancer: {
|
||||||
servers: [
|
servers: [
|
||||||
{
|
{
|
||||||
url: `http://${maintenanceHost}:${maintenancePort}`
|
url: maintenancePageUiUrl
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
passHostHeader: true
|
passHostHeader: true
|
||||||
|
|||||||
@@ -277,6 +277,8 @@ 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,
|
||||||
@@ -284,8 +286,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
|
||||||
false, // dont generate maintenance page
|
pangolinUIUrl, // dont generate maintenance page
|
||||||
false // dont generate browser gateway targets
|
pangolinUIUrl // generate browser gateway targets
|
||||||
);
|
);
|
||||||
|
|
||||||
return response(res, {
|
return response(res, {
|
||||||
|
|||||||
@@ -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 > 5) {
|
if (existingSite.lastHolePunch && now - existingSite.lastHolePunch > 12) {
|
||||||
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}?`
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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) > 5 && sitesCount > 0) {
|
if (now - (client.lastHolePunch || 0) > 12 && 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 }
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ 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";
|
||||||
@@ -22,9 +21,6 @@ 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;
|
||||||
|
|
||||||
@@ -41,7 +37,7 @@ export async function getServerInfo(
|
|||||||
return sendResponse<GetServerInfoResponse>(res, {
|
return sendResponse<GetServerInfoResponse>(res, {
|
||||||
data: {
|
data: {
|
||||||
version: APP_VERSION,
|
version: APP_VERSION,
|
||||||
supporterStatusValid,
|
supporterStatusValid: true,
|
||||||
build,
|
build,
|
||||||
enterpriseLicenseValid,
|
enterpriseLicenseValid,
|
||||||
enterpriseLicenseType
|
enterpriseLicenseType
|
||||||
|
|||||||
@@ -17,13 +17,18 @@ 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,
|
||||||
build != "oss" // generate browser gateway resources on cloud and enterprise
|
pangolinUIUrl,
|
||||||
|
pangolinUIUrl
|
||||||
);
|
);
|
||||||
|
|
||||||
if (traefikConfig?.http?.middlewares) {
|
if (traefikConfig?.http?.middlewares) {
|
||||||
|
|||||||
@@ -42,7 +42,14 @@ import {
|
|||||||
SettingsSectionFooter
|
SettingsSectionFooter
|
||||||
} from "@app/components/Settings";
|
} from "@app/components/Settings";
|
||||||
import SettingsSectionTitle from "@app/components/SettingsSectionTitle";
|
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 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";
|
||||||
@@ -50,7 +57,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 =
|
||||||
@@ -82,7 +89,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();
|
||||||
|
|
||||||
@@ -347,9 +354,7 @@ export default function LicensePage() {
|
|||||||
storageKey="license-banner-dismissed"
|
storageKey="license-banner-dismissed"
|
||||||
version={1}
|
version={1}
|
||||||
title={t("licenseBannerTitle")}
|
title={t("licenseBannerTitle")}
|
||||||
titleIcon={
|
titleIcon={<TicketCheck className="w-5 h-5 text-primary" />}
|
||||||
<TicketCheck className="w-5 h-5 text-primary" />
|
|
||||||
}
|
|
||||||
description={t("licenseBannerDescription")}
|
description={t("licenseBannerDescription")}
|
||||||
>
|
>
|
||||||
<Link
|
<Link
|
||||||
|
|||||||
@@ -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 />
|
||||||
</div>
|
{children}
|
||||||
|
</SplashImage>
|
||||||
|
<LicenseViolation />
|
||||||
</div>
|
</div>
|
||||||
</SupportStatusProvider>
|
</div>
|
||||||
|
{/* </SupportStatusProvider> */}
|
||||||
</LicenseStatusProvider>
|
</LicenseStatusProvider>
|
||||||
<Toaster />
|
<Toaster />
|
||||||
</TanstackQueryProvider>
|
</TanstackQueryProvider>
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ export default async function MaintenanceScreen() {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const headersList = await headers();
|
const headersList = await headers();
|
||||||
const host = headersList.get("host") || "";
|
const host = headersList.get("p-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>>(
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|||||||
@@ -9,33 +9,34 @@ 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">
|
<></>
|
||||||
<span
|
// <div className="relative flex items-center space-x-2 whitespace-nowrap group">
|
||||||
className="cursor-pointer"
|
// <span
|
||||||
onClick={(e) => {
|
// className="cursor-pointer"
|
||||||
// Get the bounding box of the element
|
// onClick={(e) => {
|
||||||
const rect = (
|
// // Get the bounding box of the element
|
||||||
e.target as HTMLElement
|
// const rect = (
|
||||||
).getBoundingClientRect();
|
// e.target as HTMLElement
|
||||||
|
// ).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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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} */}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -139,7 +139,6 @@ Restart=always
|
|||||||
RestartSec=2
|
RestartSec=2
|
||||||
UMask=0077
|
UMask=0077
|
||||||
|
|
||||||
NoNewPrivileges=true
|
|
||||||
PrivateTmp=true
|
PrivateTmp=true
|
||||||
|
|
||||||
[Install]
|
[Install]
|
||||||
|
|||||||
@@ -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("host") || "";
|
const host = headersList.get("p-host") || headersList.get("host") || "";
|
||||||
const hostname = host.split(":")[0];
|
const hostname = host.split(":")[0];
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|||||||
Reference in New Issue
Block a user