Merge branch 'dev' of github.com:fosrl/pangolin into dev

This commit is contained in:
Owen 2025-07-16 18:13:25 -07:00
commit 84c28645be
No known key found for this signature in database
GPG key ID: 8271FDFFD9E0CCBD
6 changed files with 88 additions and 71 deletions

View file

@ -1270,5 +1270,7 @@
"createDomainSaveTheseRecords": "Save These Records",
"createDomainSaveTheseRecordsDescription": "Make sure to save these DNS records as you will not see them again.",
"createDomainDnsPropagation": "DNS Propagation",
"createDomainDnsPropagationDescription": "DNS changes may take some time to propagate across the internet. This can take anywhere from a few minutes to 48 hours, depending on your DNS provider and TTL settings."
"createDomainDnsPropagationDescription": "DNS changes may take some time to propagate across the internet. This can take anywhere from a few minutes to 48 hours, depending on your DNS provider and TTL settings.",
"resourcePortRequired": "Port number is required for non-HTTP resources",
"resourcePortNotAllowed": "Port number should not be set for HTTP resources"
}

View file

@ -11,6 +11,8 @@ export default async function migration() {
const db = new Database(location);
try {
db.pragma("foreign_keys = OFF");
db.transaction(() => {
db.exec(`
CREATE TABLE 'clientSites' (
@ -99,8 +101,6 @@ export default async function migration() {
`);
db.pragma("foreign_keys = OFF");
db.exec(`
CREATE TABLE '__new_sites' (
'siteId' integer PRIMARY KEY AUTOINCREMENT NOT NULL,
@ -135,8 +135,6 @@ export default async function migration() {
ALTER TABLE '__new_sites' RENAME TO 'sites';
`);
db.pragma("foreign_keys = ON");
db.exec(`
ALTER TABLE 'domains' ADD 'type' text;
ALTER TABLE 'domains' ADD 'verified' integer DEFAULT 0 NOT NULL;
@ -148,7 +146,10 @@ export default async function migration() {
ALTER TABLE 'user' ADD 'twoFactorSetupRequested' integer DEFAULT 0;
ALTER TABLE 'resources' DROP COLUMN 'isBaseDomain';
`);
})(); // <-- executes the transaction immediately
})();
db.pragma("foreign_keys = ON");
console.log(`Migrated database schema`);
} catch (e) {
console.log("Unable to migrate database schema");

View file

@ -122,7 +122,20 @@ export default function GeneralForm() {
enabled: z.boolean(),
subdomain: z.string().optional(),
name: z.string().min(1).max(255),
domainId: z.string().optional()
domainId: z.string().optional(),
proxyPort: z.number().int().min(1).max(65535).optional()
}).refine((data) => {
// For non-HTTP resources, proxyPort should be defined
if (!resource.http) {
return data.proxyPort !== undefined;
}
// For HTTP resources, proxyPort should be undefined
return data.proxyPort === undefined;
}, {
message: !resource.http
? "Port number is required for non-HTTP resources"
: "Port number should not be set for HTTP resources",
path: ["proxyPort"]
});
type GeneralFormValues = z.infer<typeof GeneralFormSchema>;
@ -133,7 +146,8 @@ export default function GeneralForm() {
enabled: resource.enabled,
name: resource.name,
subdomain: resource.subdomain ? resource.subdomain : undefined,
domainId: resource.domainId || undefined
domainId: resource.domainId || undefined,
proxyPort: resource.proxyPort || undefined
},
mode: "onChange"
});
@ -196,7 +210,8 @@ export default function GeneralForm() {
enabled: data.enabled,
name: data.name,
subdomain: data.subdomain,
domainId: data.domainId
domainId: data.domainId,
proxyPort: data.proxyPort
}
)
.catch((e) => {
@ -222,7 +237,8 @@ export default function GeneralForm() {
enabled: data.enabled,
name: data.name,
subdomain: data.subdomain,
fullDomain: resource.fullDomain
fullDomain: resource.fullDomain,
proxyPort: data.proxyPort
});
router.refresh();
@ -333,6 +349,39 @@ export default function GeneralForm() {
)}
/>
{!resource.http && (
<>
<FormField
control={form.control}
name="proxyPort"
render={({ field }) => (
<FormItem>
<FormLabel>
{t("resourcePortNumber")}
</FormLabel>
<FormControl>
<Input
type="number"
value={field.value ?? ""}
onChange={(e) =>
field.onChange(
e.target.value
? parseInt(e.target.value)
: undefined
)
}
/>
</FormControl>
<FormMessage />
<FormDescription>
{t("resourcePortNumberDescription")}
</FormDescription>
</FormItem>
)}
/>
</>
)}
{resource.http && (
<div className="space-y-2">
<Label>Domain</Label>

View file

@ -25,32 +25,34 @@ export default function SiteInfoCard({}: SiteInfoCardProps) {
} else if (type === "wireguard") {
return "WireGuard";
} else if (type === "local") {
return t('local');
return t("local");
} else {
return t('unknown');
return t("unknown");
}
};
return (
<Alert>
<InfoIcon className="h-4 w-4" />
<AlertTitle className="font-semibold">{t('siteInfo')}</AlertTitle>
<AlertTitle className="font-semibold">{t("siteInfo")}</AlertTitle>
<AlertDescription className="mt-4">
<InfoSections cols={env.flags.enableClients ? 3 : 2}>
{(site.type == "newt" || site.type == "wireguard") && (
<>
<InfoSection>
<InfoSectionTitle>{t('status')}</InfoSectionTitle>
<InfoSectionTitle>
{t("status")}
</InfoSectionTitle>
<InfoSectionContent>
{site.online ? (
<div className="text-green-500 flex items-center space-x-2">
<div className="w-2 h-2 bg-green-500 rounded-full"></div>
<span>{t('online')}</span>
<span>{t("online")}</span>
</div>
) : (
<div className="text-neutral-500 flex items-center space-x-2">
<div className="w-2 h-2 bg-gray-500 rounded-full"></div>
<span>{t('offline')}</span>
<span>{t("offline")}</span>
</div>
)}
</InfoSectionContent>
@ -58,17 +60,22 @@ export default function SiteInfoCard({}: SiteInfoCardProps) {
</>
)}
<InfoSection>
<InfoSectionTitle>{t('connectionType')}</InfoSectionTitle>
<InfoSectionTitle>
{t("connectionType")}
</InfoSectionTitle>
<InfoSectionContent>
{getConnectionTypeString(site.type)}
</InfoSectionContent>
</InfoSection>
<InfoSection>
<InfoSectionTitle>Address</InfoSectionTitle>
<InfoSectionContent>
{site.address?.split("/")[0]}
</InfoSectionContent>
</InfoSection>
{env.flags.enableClients && (
<InfoSection>
<InfoSectionTitle>Address</InfoSectionTitle>
<InfoSectionContent>
{site.address?.split("/")[0]}
</InfoSectionContent>
</InfoSection>
)}
</InfoSections>
</AlertDescription>
</Alert>

View file

@ -894,49 +894,6 @@ WantedBy=default.target`
)}
</AlertDescription>
</Alert>
<Form {...form}>
<form
className="space-y-4"
id="create-site-form"
>
<FormField
control={form.control}
name="copied"
render={({ field }) => (
<FormItem>
<div className="flex items-center space-x-2">
<Checkbox
id="terms"
defaultChecked={
form.getValues(
"copied"
) as boolean
}
onCheckedChange={(
e
) => {
form.setValue(
"copied",
e as boolean
);
}}
/>
<label
htmlFor="terms"
className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
>
{t(
"siteConfirmCopy"
)}
</label>
</div>
<FormMessage />
</FormItem>
)}
/>
</form>
</Form>
</SettingsSectionBody>
</SettingsSection>
)}

View file

@ -63,6 +63,7 @@ export default function LoginForm({ redirect, onLogin, idps }: LoginFormProps) {
const [error, setError] = useState<string | null>(null);
const [loading, setLoading] = useState(false);
const [securityKeyLoading, setSecurityKeyLoading] = useState(false);
const hasIdp = idps && idps.length > 0;
const [mfaRequested, setMfaRequested] = useState(false);
@ -98,7 +99,7 @@ export default function LoginForm({ redirect, onLogin, idps }: LoginFormProps) {
async function initiateSecurityKeyAuth() {
setShowSecurityKeyPrompt(true);
setLoading(true);
setSecurityKeyLoading(true);
setError(null);
try {
@ -117,7 +118,7 @@ export default function LoginForm({ redirect, onLogin, idps }: LoginFormProps) {
// Perform WebAuthn authentication
try {
const credential = await startAuthentication(options);
// Verify authentication
const verifyRes = await api.post(
"/auth/security-key/authenticate/verify",
@ -167,7 +168,7 @@ export default function LoginForm({ redirect, onLogin, idps }: LoginFormProps) {
}));
}
} finally {
setLoading(false);
setSecurityKeyLoading(false);
setShowSecurityKeyPrompt(false);
}
}
@ -432,8 +433,8 @@ export default function LoginForm({ redirect, onLogin, idps }: LoginFormProps) {
variant="outline"
className="w-full"
onClick={initiateSecurityKeyAuth}
loading={loading}
disabled={loading || showSecurityKeyPrompt}
loading={securityKeyLoading}
disabled={securityKeyLoading || showSecurityKeyPrompt}
>
<FingerprintIcon className="w-4 h-4 mr-2" />
{t('securityKeyLogin', {