Merge dev into fix/log-analytics-adjustments

This commit is contained in:
Fred KISSIE
2025-12-10 03:19:14 +01:00
parent 9db2feff77
commit d490cab48c
555 changed files with 9375 additions and 9287 deletions

View File

@@ -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 {

View File

@@ -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);
}

View File

@@ -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[] = [];

View File

@@ -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;
}
}

View File

@@ -1,5 +1,3 @@
export function constructShareLink(
token: string
) {
export function constructShareLink(token: string) {
return `${window.location.origin}/s/${token!}`;
}

View File

@@ -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));
};

View File

@@ -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`;
}