mirror of
https://github.com/fosrl/pangolin.git
synced 2026-02-12 16:06:38 +00:00
Merge dev into fix/log-analytics-adjustments
This commit is contained in:
@@ -7,7 +7,10 @@ const patterns: PatternConfig[] = [
|
||||
{ name: "Invite Token", regex: /^\/invite\?token=[a-zA-Z0-9-]+$/ },
|
||||
{ name: "Setup", regex: /^\/setup$/ },
|
||||
{ name: "Resource Auth Portal", regex: /^\/auth\/resource\/\d+$/ },
|
||||
{ name: "Device Login", regex: /^\/auth\/login\/device(\?code=[a-zA-Z0-9-]+)?$/ }
|
||||
{
|
||||
name: "Device Login",
|
||||
regex: /^\/auth\/login\/device(\?code=[a-zA-Z0-9-]+)?$/
|
||||
}
|
||||
];
|
||||
|
||||
export function cleanRedirect(input: string, fallback?: string): string {
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
export function parseDataSize(sizeStr: string): number {
|
||||
if (typeof sizeStr !== 'string') return 0;
|
||||
if (typeof sizeStr !== "string") return 0;
|
||||
|
||||
const match = /^\s*([\d.]+)\s*([KMGT]?B)\s*$/i.exec(sizeStr);
|
||||
if (!match) return 0;
|
||||
const match = /^\s*([\d.]+)\s*([KMGT]?B)\s*$/i.exec(sizeStr);
|
||||
if (!match) return 0;
|
||||
|
||||
const [ , numStr, unitRaw ] = match;
|
||||
const num = parseFloat(numStr);
|
||||
if (isNaN(num)) return 0;
|
||||
const [, numStr, unitRaw] = match;
|
||||
const num = parseFloat(numStr);
|
||||
if (isNaN(num)) return 0;
|
||||
|
||||
const unit = unitRaw.toUpperCase();
|
||||
const multipliers = {
|
||||
B: 1,
|
||||
KB: 1024,
|
||||
MB: 1024 ** 2,
|
||||
GB: 1024 ** 3,
|
||||
TB: 1024 ** 4,
|
||||
} as const;
|
||||
const unit = unitRaw.toUpperCase();
|
||||
const multipliers = {
|
||||
B: 1,
|
||||
KB: 1024,
|
||||
MB: 1024 ** 2,
|
||||
GB: 1024 ** 3,
|
||||
TB: 1024 ** 4
|
||||
} as const;
|
||||
|
||||
return num * (multipliers[unit as keyof typeof multipliers] ?? 1);
|
||||
}
|
||||
return num * (multipliers[unit as keyof typeof multipliers] ?? 1);
|
||||
}
|
||||
|
||||
@@ -27,7 +27,9 @@ export class DockerManager {
|
||||
|
||||
async checkDockerSocket(): Promise<void> {
|
||||
try {
|
||||
const res = await this.api.post(`/site/${this.siteId}/docker/check`);
|
||||
const res = await this.api.post(
|
||||
`/site/${this.siteId}/docker/check`
|
||||
);
|
||||
console.log("Docker socket check response:", res);
|
||||
} catch (error) {
|
||||
console.error("Failed to check Docker socket:", error);
|
||||
@@ -91,9 +93,7 @@ export class DockerManager {
|
||||
};
|
||||
|
||||
try {
|
||||
await this.api.post(
|
||||
`/site/${this.siteId}/docker/trigger`
|
||||
);
|
||||
await this.api.post(`/site/${this.siteId}/docker/trigger`);
|
||||
return await fetchContainerList();
|
||||
} catch (error) {
|
||||
console.error("Failed to trigger Docker containers:", error);
|
||||
@@ -103,10 +103,10 @@ export class DockerManager {
|
||||
|
||||
async initializeDocker(): Promise<DockerState> {
|
||||
console.log(`Initializing Docker for site ID: ${this.siteId}`);
|
||||
|
||||
|
||||
// For now, assume Docker is enabled for newt sites
|
||||
const isEnabled = true;
|
||||
|
||||
|
||||
if (!isEnabled) {
|
||||
return {
|
||||
isEnabled: false,
|
||||
@@ -118,7 +118,7 @@ export class DockerManager {
|
||||
// Check and get Docker socket status
|
||||
await this.checkDockerSocket();
|
||||
const dockerStatus = await this.getDockerSocketStatus();
|
||||
|
||||
|
||||
const isAvailable = dockerStatus?.isAvailable || false;
|
||||
let containers: Container[] = [];
|
||||
|
||||
|
||||
@@ -1,29 +1,30 @@
|
||||
|
||||
export function parseHostTarget(input: string) {
|
||||
try {
|
||||
const normalized = input.match(/^(https?|h2c):\/\//) ? input : `http://${input}`;
|
||||
const url = new URL(normalized);
|
||||
try {
|
||||
const normalized = input.match(/^(https?|h2c):\/\//)
|
||||
? input
|
||||
: `http://${input}`;
|
||||
const url = new URL(normalized);
|
||||
|
||||
const protocol = url.protocol.replace(":", ""); // http | https | h2c
|
||||
const host = url.hostname;
|
||||
|
||||
let defaultPort: number;
|
||||
switch (protocol) {
|
||||
case "https":
|
||||
defaultPort = 443;
|
||||
break;
|
||||
case "h2c":
|
||||
defaultPort = 80;
|
||||
break;
|
||||
default: // http
|
||||
defaultPort = 80;
|
||||
break;
|
||||
const protocol = url.protocol.replace(":", ""); // http | https | h2c
|
||||
const host = url.hostname;
|
||||
|
||||
let defaultPort: number;
|
||||
switch (protocol) {
|
||||
case "https":
|
||||
defaultPort = 443;
|
||||
break;
|
||||
case "h2c":
|
||||
defaultPort = 80;
|
||||
break;
|
||||
default: // http
|
||||
defaultPort = 80;
|
||||
break;
|
||||
}
|
||||
|
||||
const port = url.port ? parseInt(url.port, 10) : defaultPort;
|
||||
|
||||
return { protocol, host, port };
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
|
||||
const port = url.port ? parseInt(url.port, 10) : defaultPort;
|
||||
|
||||
return { protocol, host, port };
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
export function constructShareLink(
|
||||
token: string
|
||||
) {
|
||||
export function constructShareLink(token: string) {
|
||||
return `${window.location.origin}/s/${token!}`;
|
||||
}
|
||||
|
||||
@@ -1,63 +1,67 @@
|
||||
|
||||
export type DomainType = "organization" | "provided" | "provided-search";
|
||||
|
||||
export const SINGLE_LABEL_RE = /^[\p{L}\p{N}-]+$/u; // provided-search (no dots)
|
||||
export const MULTI_LABEL_RE = /^[\p{L}\p{N}-]+(\.[\p{L}\p{N}-]+)*$/u; // ns/wildcard
|
||||
export const SINGLE_LABEL_STRICT_RE = /^[\p{L}\p{N}](?:[\p{L}\p{N}-]*[\p{L}\p{N}])?$/u; // start/end alnum
|
||||
|
||||
export const SINGLE_LABEL_STRICT_RE =
|
||||
/^[\p{L}\p{N}](?:[\p{L}\p{N}-]*[\p{L}\p{N}])?$/u; // start/end alnum
|
||||
|
||||
export function sanitizeInputRaw(input: string): string {
|
||||
if (!input) return "";
|
||||
return input
|
||||
.toLowerCase()
|
||||
.normalize("NFC") // normalize Unicode
|
||||
.replace(/[^\p{L}\p{N}.-]/gu, ""); // allow Unicode letters, numbers, dot, hyphen
|
||||
if (!input) return "";
|
||||
return input
|
||||
.toLowerCase()
|
||||
.normalize("NFC") // normalize Unicode
|
||||
.replace(/[^\p{L}\p{N}.-]/gu, ""); // allow Unicode letters, numbers, dot, hyphen
|
||||
}
|
||||
|
||||
export function finalizeSubdomainSanitize(input: string): string {
|
||||
if (!input) return "";
|
||||
return input
|
||||
.toLowerCase()
|
||||
.normalize("NFC")
|
||||
.replace(/[^\p{L}\p{N}.-]/gu, "") // allow Unicode
|
||||
.replace(/\.{2,}/g, ".") // collapse multiple dots
|
||||
.replace(/^-+|-+$/g, "") // strip leading/trailing hyphens
|
||||
.replace(/^\.+|\.+$/g, "") // strip leading/trailing dots
|
||||
.replace(/(\.-)|(-\.)/g, "."); // fix illegal dot-hyphen combos
|
||||
if (!input) return "";
|
||||
return input
|
||||
.toLowerCase()
|
||||
.normalize("NFC")
|
||||
.replace(/[^\p{L}\p{N}.-]/gu, "") // allow Unicode
|
||||
.replace(/\.{2,}/g, ".") // collapse multiple dots
|
||||
.replace(/^-+|-+$/g, "") // strip leading/trailing hyphens
|
||||
.replace(/^\.+|\.+$/g, "") // strip leading/trailing dots
|
||||
.replace(/(\.-)|(-\.)/g, "."); // fix illegal dot-hyphen combos
|
||||
}
|
||||
|
||||
|
||||
export function validateByDomainType(subdomain: string, domainType: { type: "provided-search" | "organization"; domainType?: "ns" | "cname" | "wildcard" } ): boolean {
|
||||
if (!domainType) return false;
|
||||
|
||||
if (domainType.type === "provided-search") {
|
||||
return SINGLE_LABEL_RE.test(subdomain);
|
||||
}
|
||||
|
||||
if (domainType.type === "organization") {
|
||||
if (domainType.domainType === "cname") {
|
||||
return subdomain === "";
|
||||
} else if (domainType.domainType === "ns" || domainType.domainType === "wildcard") {
|
||||
if (subdomain === "") return true;
|
||||
if (!MULTI_LABEL_RE.test(subdomain)) return false;
|
||||
const labels = subdomain.split(".");
|
||||
return labels.every(l => l.length >= 1 && l.length <= 63 && SINGLE_LABEL_RE.test(l));
|
||||
export function validateByDomainType(
|
||||
subdomain: string,
|
||||
domainType: {
|
||||
type: "provided-search" | "organization";
|
||||
domainType?: "ns" | "cname" | "wildcard";
|
||||
}
|
||||
}
|
||||
return false;
|
||||
): boolean {
|
||||
if (!domainType) return false;
|
||||
|
||||
if (domainType.type === "provided-search") {
|
||||
return SINGLE_LABEL_RE.test(subdomain);
|
||||
}
|
||||
|
||||
if (domainType.type === "organization") {
|
||||
if (domainType.domainType === "cname") {
|
||||
return subdomain === "";
|
||||
} else if (
|
||||
domainType.domainType === "ns" ||
|
||||
domainType.domainType === "wildcard"
|
||||
) {
|
||||
if (subdomain === "") return true;
|
||||
if (!MULTI_LABEL_RE.test(subdomain)) return false;
|
||||
const labels = subdomain.split(".");
|
||||
return labels.every(
|
||||
(l) =>
|
||||
l.length >= 1 && l.length <= 63 && SINGLE_LABEL_RE.test(l)
|
||||
);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
export const isValidSubdomainStructure = (input: string): boolean => {
|
||||
const regex = /^(?!-)([\p{L}\p{N}-]{1,63})(?<!-)$/u;
|
||||
const regex = /^(?!-)([\p{L}\p{N}-]{1,63})(?<!-)$/u;
|
||||
|
||||
if (!input) return false;
|
||||
if (input.includes("..")) return false;
|
||||
if (!input) return false;
|
||||
if (input.includes("..")) return false;
|
||||
|
||||
return input.split(".").every(label => regex.test(label));
|
||||
return input.split(".").every((label) => regex.test(label));
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -28,7 +28,10 @@ export function generateObfuscatedWireGuardConfig(options?: {
|
||||
listenPort?: number | string | null;
|
||||
publicKey?: string | null;
|
||||
}): string {
|
||||
const obfuscate = (value: string | null | undefined, length: number = 20): string => {
|
||||
const obfuscate = (
|
||||
value: string | null | undefined,
|
||||
length: number = 20
|
||||
): string => {
|
||||
return value || "•".repeat(length);
|
||||
};
|
||||
|
||||
@@ -37,13 +40,19 @@ export function generateObfuscatedWireGuardConfig(options?: {
|
||||
};
|
||||
|
||||
const subnet = options?.subnet || obfuscate(null, 20);
|
||||
const subnetWithCidr = subnet.includes("•")
|
||||
? `${subnet}/32`
|
||||
: (subnet.includes("/") ? subnet : `${subnet}/32`);
|
||||
const address = options?.address ? options.address.split("/")[0] : obfuscate(null, 20);
|
||||
const subnetWithCidr = subnet.includes("•")
|
||||
? `${subnet}/32`
|
||||
: subnet.includes("/")
|
||||
? subnet
|
||||
: `${subnet}/32`;
|
||||
const address = options?.address
|
||||
? options.address.split("/")[0]
|
||||
: obfuscate(null, 20);
|
||||
const endpoint = obfuscate(options?.endpoint, 20);
|
||||
const listenPort = options?.listenPort
|
||||
? (typeof options.listenPort === "number" ? options.listenPort : options.listenPort)
|
||||
const listenPort = options?.listenPort
|
||||
? typeof options.listenPort === "number"
|
||||
? options.listenPort
|
||||
: options.listenPort
|
||||
: 51820;
|
||||
const publicKey = obfuscateKey(options?.publicKey);
|
||||
|
||||
@@ -58,4 +67,3 @@ AllowedIPs = ${address}/32
|
||||
Endpoint = ${endpoint}:${listenPort}
|
||||
PersistentKeepalive = 5`;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user