Merge pull request #651 from fosrl/dev

Dev
This commit is contained in:
Milo Schwartz 2025-05-03 13:07:18 -04:00 committed by GitHub
commit 21f1326045
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 40 additions and 54 deletions

View file

@ -17,7 +17,7 @@ function detectIpVersion(ip: string): IPVersion {
*/ */
function ipToBigInt(ip: string): bigint { function ipToBigInt(ip: string): bigint {
const version = detectIpVersion(ip); const version = detectIpVersion(ip);
if (version === 4) { if (version === 4) {
return ip.split('.') return ip.split('.')
.reduce((acc, octet) => { .reduce((acc, octet) => {
@ -105,7 +105,7 @@ export function cidrToRange(cidr: string): IPRange {
const version = detectIpVersion(ip); const version = detectIpVersion(ip);
const prefixBits = parseInt(prefix); const prefixBits = parseInt(prefix);
const ipBigInt = ipToBigInt(ip); const ipBigInt = ipToBigInt(ip);
// Validate prefix length // Validate prefix length
const maxPrefix = version === 4 ? 32 : 128; const maxPrefix = version === 4 ? 32 : 128;
if (prefixBits < 0 || prefixBits > maxPrefix) { if (prefixBits < 0 || prefixBits > maxPrefix) {
@ -116,7 +116,7 @@ export function cidrToRange(cidr: string): IPRange {
const mask = BigInt.asUintN(version === 4 ? 64 : 128, (BigInt(1) << shiftBits) - BigInt(1)); const mask = BigInt.asUintN(version === 4 ? 64 : 128, (BigInt(1) << shiftBits) - BigInt(1));
const start = ipBigInt & ~mask; const start = ipBigInt & ~mask;
const end = start | mask; const end = start | mask;
return { start, end }; return { start, end };
} }
@ -136,17 +136,17 @@ export function findNextAvailableCidr(
if (!startCidr && existingCidrs.length === 0) { if (!startCidr && existingCidrs.length === 0) {
return null; return null;
} }
// If no existing CIDRs, use the IP version from startCidr // If no existing CIDRs, use the IP version from startCidr
const version = startCidr const version = startCidr
? detectIpVersion(startCidr.split('/')[0]) ? detectIpVersion(startCidr.split('/')[0])
: 4; // Default to IPv4 if no startCidr provided : 4; // Default to IPv4 if no startCidr provided
// Use appropriate default startCidr if none provided // Use appropriate default startCidr if none provided
startCidr = startCidr || (version === 4 ? "0.0.0.0/0" : "::/0"); startCidr = startCidr || (version === 4 ? "0.0.0.0/0" : "::/0");
// If there are existing CIDRs, ensure all are same version // If there are existing CIDRs, ensure all are same version
if (existingCidrs.length > 0 && if (existingCidrs.length > 0 &&
existingCidrs.some(cidr => detectIpVersion(cidr.split('/')[0]) !== version)) { existingCidrs.some(cidr => detectIpVersion(cidr.split('/')[0]) !== version)) {
throw new Error('All CIDRs must be of the same IP version'); throw new Error('All CIDRs must be of the same IP version');
} }
@ -196,12 +196,14 @@ export function findNextAvailableCidr(
export function isIpInCidr(ip: string, cidr: string): boolean { export function isIpInCidr(ip: string, cidr: string): boolean {
const ipVersion = detectIpVersion(ip); const ipVersion = detectIpVersion(ip);
const cidrVersion = detectIpVersion(cidr.split('/')[0]); const cidrVersion = detectIpVersion(cidr.split('/')[0]);
// If IP versions don't match, the IP cannot be in the CIDR range
if (ipVersion !== cidrVersion) { if (ipVersion !== cidrVersion) {
throw new Error('IP address and CIDR must be of the same version'); // throw new Erorr
return false;
} }
const ipBigInt = ipToBigInt(ip); const ipBigInt = ipToBigInt(ip);
const range = cidrToRange(cidr); const range = cidrToRange(cidr);
return ipBigInt >= range.start && ipBigInt <= range.end; return ipBigInt >= range.start && ipBigInt <= range.end;
} }

View file

@ -28,7 +28,7 @@ const bodySchema = z
.strict(); .strict();
const ensureTrailingSlash = (url: string): string => { const ensureTrailingSlash = (url: string): string => {
return url.endsWith('/') ? url : `${url}/`; return url;
}; };
export type GenerateOidcUrlResponse = { export type GenerateOidcUrlResponse = {

View file

@ -23,7 +23,7 @@ import { oidcAutoProvision } from "./oidcAutoProvision";
import license from "@server/license/license"; import license from "@server/license/license";
const ensureTrailingSlash = (url: string): string => { const ensureTrailingSlash = (url: string): string => {
return url.endsWith("/") ? url : `${url}/`; return url;
}; };
const paramsSchema = z const paramsSchema = z
@ -243,7 +243,7 @@ export async function validateOidcCallback(
return next( return next(
createHttpError( createHttpError(
HttpCode.UNAUTHORIZED, HttpCode.UNAUTHORIZED,
"User not provisioned in the system" `User with username ${userIdentifier} is unprovisioned. This user must be added to an organization before logging in.`
) )
); );
} }

View file

@ -363,12 +363,12 @@ export default function ReverseProxyTargets(props: {
setHttpsTlsLoading(true); setHttpsTlsLoading(true);
await api.post(`/resource/${params.resourceId}`, { await api.post(`/resource/${params.resourceId}`, {
ssl: data.ssl, ssl: data.ssl,
tlsServerName: data.tlsServerName || undefined tlsServerName: data.tlsServerName || null
}); });
updateResource({ updateResource({
...resource, ...resource,
ssl: data.ssl, ssl: data.ssl,
tlsServerName: data.tlsServerName || undefined tlsServerName: data.tlsServerName || null
}); });
toast({ toast({
title: "TLS settings updated", title: "TLS settings updated",
@ -393,11 +393,11 @@ export default function ReverseProxyTargets(props: {
try { try {
setProxySettingsLoading(true); setProxySettingsLoading(true);
await api.post(`/resource/${params.resourceId}`, { await api.post(`/resource/${params.resourceId}`, {
setHostHeader: data.setHostHeader || undefined setHostHeader: data.setHostHeader || null
}); });
updateResource({ updateResource({
...resource, ...resource,
setHostHeader: data.setHostHeader || undefined setHostHeader: data.setHostHeader || null
}); });
toast({ toast({
title: "Proxy settings updated", title: "Proxy settings updated",

View file

@ -173,13 +173,15 @@ export default function Page() {
if (httpData.isBaseDomain) { if (httpData.isBaseDomain) {
Object.assign(payload, { Object.assign(payload, {
domainId: httpData.domainId, domainId: httpData.domainId,
isBaseDomain: true isBaseDomain: true,
protocol: "tcp"
}); });
} else { } else {
Object.assign(payload, { Object.assign(payload, {
subdomain: httpData.subdomain, subdomain: httpData.subdomain,
domainId: httpData.domainId, domainId: httpData.domainId,
isBaseDomain: false isBaseDomain: false,
protocol: "tcp"
}); });
} }
} else { } else {

View file

@ -137,8 +137,8 @@ export function SitePriceCalculator({
</div> </div>
<p className="text-muted-foreground text-sm mt-2 text-center"> <p className="text-muted-foreground text-sm mt-2 text-center">
For the most up-to-date pricing, please visit For the most up-to-date pricing and discounts,
our{" "} please visit the{" "}
<a <a
href="https://docs.fossorial.io/pricing" href="https://docs.fossorial.io/pricing"
target="_blank" target="_blank"

View file

@ -452,6 +452,12 @@ export default function LicensePage() {
in system in system
</div> </div>
</div> </div>
{!licenseStatus?.isHostLicensed && (
<p className="text-sm text-muted-foreground">
There is no limit on the number of sites
using an unlicensed host.
</p>
)}
{licenseStatus?.maxSites && ( {licenseStatus?.maxSites && (
<div className="space-y-2"> <div className="space-y-2">
<div className="flex justify-between text-sm"> <div className="flex justify-between text-sm">

View file

@ -16,33 +16,7 @@ export function Breadcrumbs() {
const breadcrumbs: BreadcrumbItem[] = segments.map((segment, index) => { const breadcrumbs: BreadcrumbItem[] = segments.map((segment, index) => {
const href = `/${segments.slice(0, index + 1).join("/")}`; const href = `/${segments.slice(0, index + 1).join("/")}`;
let label = segment; let label = decodeURIComponent(segment);
// // Format labels
// if (segment === "settings") {
// label = "Settings";
// } else if (segment === "sites") {
// label = "Sites";
// } else if (segment === "resources") {
// label = "Resources";
// } else if (segment === "access") {
// label = "Access Control";
// } else if (segment === "general") {
// label = "General";
// } else if (segment === "share-links") {
// label = "Shareable Links";
// } else if (segment === "users") {
// label = "Users";
// } else if (segment === "roles") {
// label = "Roles";
// } else if (segment === "invitations") {
// label = "Invitations";
// } else if (segment === "proxy") {
// label = "proxy";
// } else if (segment === "authentication") {
// label = "Authentication";
// }
return { label, href }; return { label, href };
}); });

View file

@ -189,10 +189,12 @@ export default function SupporterStatus() {
<CredenzaBody> <CredenzaBody>
<p> <p>
Purchase a supporter key to help us continue Purchase a supporter key to help us continue
developing Pangolin. Your contribution allows us developing Pangolin for the community. Your
commit more time to maintain and add new features to contribution allows us to commit more time to
the application for everyone. We will never use this maintain and add new features to the application for
to paywall features. everyone. We will never use this to paywall
features. This is separate from the Professional
Edition.
</p> </p>
<p> <p>

View file

@ -16,7 +16,7 @@ const ToastViewport = React.forwardRef<
<ToastPrimitives.Viewport <ToastPrimitives.Viewport
ref={ref} ref={ref}
className={cn( className={cn(
"fixed top-0 z-[100] flex max-h-screen w-full flex-col-reverse p-4 sm:bottom-0 sm:right-0 sm:top-auto sm:flex-col md:max-w-[420px]", "fixed top-0 right-0 z-[100] flex max-h-screen w-full flex-col-reverse p-4 md:max-w-[420px]",
className className
)} )}
{...props} {...props}