Merge pull request #3245 from fosrl/dev

Bugfixes
This commit is contained in:
Owen Schwartz
2026-06-11 16:17:41 -07:00
committed by GitHub
10 changed files with 214 additions and 224 deletions

View File

@@ -34,4 +34,5 @@ build.ts
tsconfig.json
Dockerfile*
drizzle.config.ts
allowedDevOrigins.json
allowedDevOrigins.json
scratch/

View File

@@ -984,7 +984,7 @@
"sharedPolicy": "Shared Policy",
"sharedPolicyNoneDescription": "This resource has its own policy.",
"resourceSharedPolicyOwnDescription": "This resource has its own authentication and access rules controls.",
"resourceSharedPolicyInheritedDescription": "This resource inherits authentication and access rules controls from <policyLink>{policyName}</policyLink>.",
"resourceSharedPolicyInheritedDescription": "This resource inherits from <policyLink>{policyName}</policyLink>.",
"resourceSharedPolicyAuthenticationNotice": "This resource is using a shared policy. Some authentication settings can be edited on this resource to add to the policy. To change the underlying policy, you must edit to <policyLink>{policyName}</policyLink>.",
"resourceSharedPolicyRulesNotice": "This resource is using a shared policy. Some access rules can be edited on this resource. To change the underlying policy, you must edit <policyLink>{policyName}</policyLink>.",
"resourceUsersRoles": "Access Controls",

View File

@@ -228,7 +228,7 @@ export default async function migration() {
).run();
db.prepare(
`
UPDATE 'siteResources' SET 'destination2' = 'destination';
UPDATE 'siteResources' SET "destination2" = "destination";
`
).run();
db.prepare(
@@ -349,9 +349,9 @@ export default async function migration() {
db.prepare(
`
UPDATE 'targets'
SET 'mode' = (
SELECT 'mode' FROM 'resources'
WHERE 'resources'.'resourceId' = 'targets'.'resourceId'
SET "mode" = (
SELECT "mode" FROM 'resources'
WHERE "resources"."resourceId" = "targets"."resourceId"
);
`
).run();

View File

@@ -43,6 +43,7 @@ import { usePaidStatus } from "@app/hooks/usePaidStatus";
import { tierMatrix, TierFeature } from "@server/lib/billing/tierMatrix";
import { PaidFeaturesAlert } from "@app/components/PaidFeaturesAlert";
import { ExternalLink } from "lucide-react";
import { env } from "process";
// Schema for general organization settings
const GeneralFormSchema = z.object({
@@ -165,6 +166,7 @@ function DeleteForm({ org }: SectionFormProps) {
function GeneralSectionForm({ org }: SectionFormProps) {
const { updateOrg } = useOrgContext();
const { env } = useEnvContext();
const form = useForm({
resolver: zodResolver(
GeneralFormSchema.pick({
@@ -265,36 +267,42 @@ function GeneralSectionForm({ org }: SectionFormProps) {
<PaidFeaturesAlert
tiers={tierMatrix.newtAutoUpdate}
/>
<FormField
control={form.control}
name="settingsEnableGlobalNewtAutoUpdate"
render={({ field }) => (
<FormItem>
<FormControl>
<SwitchInput
id="settings-enable-global-newt-auto-update"
label={t("newtAutoUpdate")}
checked={field.value}
onCheckedChange={field.onChange}
disabled={!hasAutoUpdateFeature}
/>
</FormControl>
<FormDescription>
{t("newtAutoUpdateDescription")}{" "}
<a
href="https://docs.pangolin.net/manage/sites/auto-update"
target="_blank"
rel="noopener noreferrer"
className="text-primary hover:underline inline-flex items-center gap-1"
>
{t("learnMore")}
<ExternalLink className="size-3.5 shrink-0" />
</a>
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
{!env.flags.disableEnterpriseFeatures && (
<FormField
control={form.control}
name="settingsEnableGlobalNewtAutoUpdate"
render={({ field }) => (
<FormItem>
<FormControl>
<SwitchInput
id="settings-enable-global-newt-auto-update"
label={t("newtAutoUpdate")}
checked={field.value}
onCheckedChange={
field.onChange
}
disabled={
!hasAutoUpdateFeature
}
/>
</FormControl>
<FormDescription>
{t("newtAutoUpdateDescription")}{" "}
<a
href="https://docs.pangolin.net/manage/sites/auto-update"
target="_blank"
rel="noopener noreferrer"
className="text-primary hover:underline inline-flex items-center gap-1"
>
{t("learnMore")}
<ExternalLink className="size-3.5 shrink-0" />
</a>
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
)}
</form>
</Form>
</SettingsSectionForm>

View File

@@ -455,7 +455,7 @@ export default function GeneralForm() {
)}
{ !["tcp", "udp"].includes(
resource.mode
) && (
) && !env.flags.disableEnterpriseFeatures && (
<>
<SettingsFormCell span="full">
<SettingsSubsectionHeader>

View File

@@ -169,20 +169,27 @@ export default function ResourceMaintenancePage() {
{
id: "automatic",
title: `${t("automatic")} (${t("recommended")})`,
description: t("automaticModeDescription"),
disabled: isMaintenanceDisabled
description: t("automaticModeDescription")
},
{
id: "forced",
title: t("forced"),
description: t("forcedModeDescription"),
disabled: isMaintenanceDisabled
description: t("forcedModeDescription")
}
];
return (
<SettingsContainer>
<SettingsSection>
<>
<PaidFeaturesAlert tiers={tierMatrix.maintencePage} />
<div
className={
isMaintenanceDisabled
? "pointer-events-none opacity-50"
: undefined
}
>
<SettingsContainer>
<SettingsSection>
<SettingsSectionHeader>
<SettingsSectionTitle>
{t("maintenanceMode")}
@@ -193,7 +200,6 @@ export default function ResourceMaintenancePage() {
</SettingsSectionHeader>
<SettingsSectionBody>
<PaidFeaturesAlert tiers={tierMatrix.maintencePage} />
<SettingsSectionForm variant="half">
<Form {...maintenanceForm}>
<form
@@ -205,46 +211,33 @@ export default function ResourceMaintenancePage() {
<FormField
control={maintenanceForm.control}
name="maintenanceModeEnabled"
render={({ field }) => {
const isDisabled = !isPaidUser(
tierMatrix.maintencePage
);
return (
<FormItem>
<FormControl>
<SwitchInput
id="enable-maintenance"
checked={
field.value
}
label={t(
"enableMaintenanceMode"
)}
description={t(
"enableMaintenanceModeDescription"
)}
disabled={
isDisabled
}
onCheckedChange={(
render={({ field }) => (
<FormItem>
<FormControl>
<SwitchInput
id="enable-maintenance"
checked={
field.value
}
label={t(
"enableMaintenanceMode"
)}
description={t(
"enableMaintenanceModeDescription"
)}
onCheckedChange={(
val
) => {
maintenanceForm.setValue(
"maintenanceModeEnabled",
val
) => {
if (
!isDisabled
) {
maintenanceForm.setValue(
"maintenanceModeEnabled",
val
);
}
}}
/>
</FormControl>
<FormMessage />
</FormItem>
);
}}
);
}}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</SettingsFormCell>
@@ -329,11 +322,6 @@ export default function ResourceMaintenancePage() {
<FormControl>
<Input
{...field}
disabled={
!isPaidUser(
tierMatrix.maintencePage
)
}
placeholder="We'll be back soon!"
/>
</FormControl>
@@ -365,11 +353,6 @@ export default function ResourceMaintenancePage() {
<Textarea
{...field}
rows={4}
disabled={
!isPaidUser(
tierMatrix.maintencePage
)
}
placeholder={t(
"maintenancePageMessagePlaceholder"
)}
@@ -402,11 +385,6 @@ export default function ResourceMaintenancePage() {
<FormControl>
<Input
{...field}
disabled={
!isPaidUser(
tierMatrix.maintencePage
)
}
placeholder={t(
"maintenanceTime"
)}
@@ -430,20 +408,19 @@ export default function ResourceMaintenancePage() {
</SettingsSectionForm>
</SettingsSectionBody>
<SettingsSectionFooter>
<Button
type="submit"
loading={maintenanceSaveLoading}
disabled={
maintenanceSaveLoading ||
!isPaidUser(tierMatrix.maintencePage)
}
form="maintenance-settings-form"
>
{t("saveSettings")}
</Button>
</SettingsSectionFooter>
</SettingsSection>
</SettingsContainer>
<SettingsSectionFooter>
<Button
type="submit"
loading={maintenanceSaveLoading}
disabled={maintenanceSaveLoading}
form="maintenance-settings-form"
>
{t("saveSettings")}
</Button>
</SettingsSectionFooter>
</SettingsSection>
</SettingsContainer>
</div>
</>
);
}

View File

@@ -253,85 +253,87 @@ export default function GeneralPage() {
<PaidFeaturesAlert
tiers={tierMatrix.newtAutoUpdate}
/>
{site && site.type === "newt" && (
<FormField
control={form.control}
name="autoUpdateEnabled"
render={({ field }) => {
const isOverriding = form.watch(
"autoUpdateOverrideOrg"
);
return (
<FormItem>
<FormControl>
<div className="">
<SwitchInput
id="auto-update-enabled"
label={t(
"siteAutoUpdateLabel"
)}
checked={
field.value
}
onCheckedChange={(
checked
) => {
field.onChange(
{site &&
site.type === "newt" &&
!env.flags.disableEnterpriseFeatures && (
<FormField
control={form.control}
name="autoUpdateEnabled"
render={({ field }) => {
const isOverriding = form.watch(
"autoUpdateOverrideOrg"
);
return (
<FormItem>
<FormControl>
<div className="">
<SwitchInput
id="auto-update-enabled"
label={t(
"siteAutoUpdateLabel"
)}
checked={
field.value
}
onCheckedChange={(
checked
);
form.setValue(
"autoUpdateOverrideOrg",
true
);
}}
disabled={
!hasAutoUpdateFeature
}
/>
{isOverriding && (
<ButtonUI
type="button"
variant="link"
size="sm"
className="text-sm text-muted-foreground px-0"
onClick={() => {
) => {
field.onChange(
checked
);
form.setValue(
"autoUpdateOverrideOrg",
false
);
form.setValue(
"autoUpdateEnabled",
orgAutoUpdate
true
);
}}
>
{t(
"siteAutoUpdateResetToOrg"
)}
</ButtonUI>
)}
</div>
</FormControl>
<FormDescription>
{t(
"siteAutoUpdateDescription"
)}{" "}
<a
href="https://docs.pangolin.net/manage/sites/auto-update"
target="_blank"
rel="noopener noreferrer"
className="text-primary hover:underline inline-flex items-center gap-1"
>
{t("learnMore")}
<ExternalLink className="size-3.5 shrink-0" />
</a>
</FormDescription>
<FormMessage />
</FormItem>
);
}}
/>
)}
disabled={
!hasAutoUpdateFeature
}
/>
{isOverriding && (
<ButtonUI
type="button"
variant="link"
size="sm"
className="text-sm text-muted-foreground px-0"
onClick={() => {
form.setValue(
"autoUpdateOverrideOrg",
false
);
form.setValue(
"autoUpdateEnabled",
orgAutoUpdate
);
}}
>
{t(
"siteAutoUpdateResetToOrg"
)}
</ButtonUI>
)}
</div>
</FormControl>
<FormDescription>
{t(
"siteAutoUpdateDescription"
)}{" "}
<a
href="https://docs.pangolin.net/manage/sites/auto-update"
target="_blank"
rel="noopener noreferrer"
className="text-primary hover:underline inline-flex items-center gap-1"
>
{t("learnMore")}
<ExternalLink className="size-3.5 shrink-0" />
</a>
</FormDescription>
<FormMessage />
</FormItem>
);
}}
/>
)}
</form>
</Form>
</SettingsSectionForm>

View File

@@ -23,7 +23,7 @@ import {
} from "@app/components/ui/form";
import HeaderTitle from "@app/components/SettingsSectionTitle";
import { z } from "zod";
import { createElement, useEffect, useState } from "react";
import { useEffect, useState } from "react";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { Input } from "@app/components/ui/input";
@@ -37,15 +37,6 @@ import {
InfoSections,
InfoSectionTitle
} from "@app/components/InfoSection";
import {
FaApple,
FaCubes,
FaDocker,
FaFreebsd,
FaWindows
} from "react-icons/fa";
import { SiNixos, SiKubernetes } from "react-icons/si";
import { Checkbox, CheckboxWithLabel } from "@app/components/ui/checkbox";
import { Alert, AlertDescription, AlertTitle } from "@app/components/ui/alert";
import { generateKeypair } from "../[niceId]/wireguardConfig";
import { createApiClient, formatAxiosError } from "@app/lib/api";

View File

@@ -156,10 +156,11 @@ export const orgNavSections = (
]
: []),
// PaidFeaturesAlert
...((build === "oss" && !env?.flags.disableEnterpriseFeatures) ||
build === "saas" ||
env?.app.identityProviderMode === "org" ||
(env?.app.identityProviderMode === undefined && build !== "oss")
...(!env?.flags.disableEnterpriseFeatures &&
(build === "saas" ||
env?.app.identityProviderMode === "org" ||
(env?.app.identityProviderMode === undefined &&
build !== "oss"))
? [
{
title: "sidebarIdentityProviders",

View File

@@ -20,6 +20,7 @@ import {
} from "react-icons/fa";
import { ExternalLink } from "lucide-react";
import { SiKubernetes, SiNixos } from "react-icons/si";
import { useEnvContext } from "@app/hooks/useEnvContext";
export type CommandItem = string | { title: string; command: string };
@@ -50,9 +51,12 @@ export function NewtSiteInstallCommands({
version = "latest"
}: NewtSiteInstallCommandsProps) {
const t = useTranslations();
const { env } = useEnvContext();
const [acceptClients, setAcceptClients] = useState(true);
const [allowPangolinSsh, setAllowPangolinSsh] = useState(true);
const [allowPangolinSsh, setAllowPangolinSsh] = useState(
!env.flags.disableEnterpriseFeatures
);
const [platform, setPlatform] = useState<Platform>("linux");
const [architecture, setArchitecture] = useState(
() => getArchitectures(platform)[0]
@@ -71,7 +75,11 @@ export function NewtSiteInstallCommands({
: "";
const disableSshFlag =
supportsSshOption && !allowPangolinSsh ? " --disable-ssh" : "";
supportsSshOption &&
!allowPangolinSsh &&
!env.flags.disableEnterpriseFeatures
? " --disable-ssh"
: "";
const runAsRootPrefix =
supportsSshOption && allowPangolinSsh ? "sudo " : "";
@@ -306,27 +314,29 @@ WantedBy=default.target`
>
{t("siteAcceptClientConnectionsDescription")}
</p>
{supportsSshOption && (
<>
<div className="flex items-center space-x-2 mb-2 mt-2">
<CheckboxWithLabel
id="allowPangolinSsh"
checked={allowPangolinSsh}
onCheckedChange={(checked) => {
const value = checked as boolean;
setAllowPangolinSsh(value);
}}
label="Allow Pangolin SSH"
/>
</div>
<p
id="allowPangolinSsh-desc"
className="text-sm text-muted-foreground"
>
{t("sitePangolinSshDescription")}
</p>
</>
)}
{supportsSshOption &&
!env.flags.disableEnterpriseFeatures && (
<>
<div className="flex items-center space-x-2 mb-2 mt-2">
<CheckboxWithLabel
id="allowPangolinSsh"
checked={allowPangolinSsh}
onCheckedChange={(checked) => {
const value =
checked as boolean;
setAllowPangolinSsh(value);
}}
label="Allow Pangolin SSH"
/>
</div>
<p
id="allowPangolinSsh-desc"
className="text-sm text-muted-foreground"
>
{t("sitePangolinSshDescription")}
</p>
</>
)}
</div>
)}