Compare commits

...

18 Commits

Author SHA1 Message Date
Owen Schwartz
d9952b0762 Merge pull request #3250 from fosrl/dev
1.19.1
2026-06-11 22:05:24 -07:00
Owen
935593885a Adjust 1.19 and add 1.19.1 to ensure sso not null 2026-06-11 22:01:20 -07:00
miloschwartz
3fcfd3304f fix address input width 2026-06-11 18:34:22 -07:00
Owen Schwartz
6e271028f3 Merge pull request #3245 from fosrl/dev
Bugfixes
2026-06-11 16:17:41 -07:00
Owen
820f66e58f Properly hide things with disable enterprise flag 2026-06-11 16:10:29 -07:00
Owen
b0fdc10e06 Properly hide things with disable enterprise flag 2026-06-11 16:01:32 -07:00
miloschwartz
b82b41ed26 fix migration 2026-06-11 15:02:29 -07:00
miloschwartz
3e977ba00d make paid alert position more consistent on resource 2026-06-11 12:38:08 -07:00
Owen Schwartz
a724b07846 Merge pull request #3244 from fosrl/dev
fix paywalling
2026-06-11 12:27:49 -07:00
Owen
5f0bc71bcd Merge branch 'main' into dev 2026-06-11 12:26:31 -07:00
miloschwartz
aea7827c1a fix paywalling 2026-06-11 12:26:01 -07:00
Owen Schwartz
d865c4c55b Merge pull request #3242 from fosrl/dev
Use ssh like mode host
2026-06-11 11:29:45 -07:00
Owen Schwartz
cfe33eb974 Merge pull request #3241 from fosrl/dev
dev
2026-06-10 21:47:44 -07:00
Owen Schwartz
3cc244a1d3 Merge pull request #3240 from fosrl/dev
Fix small bugs with paid features, ui, docs
2026-06-10 20:49:59 -07:00
Owen Schwartz
10542d7282 Merge pull request #3239 from fosrl/dev
1.19.0
2026-06-10 16:50:32 -07:00
Owen Schwartz
7fa1180d10 Merge pull request #3221 from fosrl/dev
1.19.0-rc.1
2026-06-04 15:45:27 -07:00
Owen Schwartz
8b50f1fb65 Merge pull request #3218 from fosrl/dev
Fix installer
2026-06-04 11:21:59 -07:00
Owen Schwartz
527d4cc777 Merge pull request #3215 from fosrl/dev
1.19.0-rc.0
2026-06-04 10:34:20 -07:00
15 changed files with 357 additions and 321 deletions

View File

@@ -35,3 +35,4 @@ tsconfig.json
Dockerfile* Dockerfile*
drizzle.config.ts drizzle.config.ts
allowedDevOrigins.json allowedDevOrigins.json
scratch/

View File

@@ -984,7 +984,7 @@
"sharedPolicy": "Shared Policy", "sharedPolicy": "Shared Policy",
"sharedPolicyNoneDescription": "This resource has its own policy.", "sharedPolicyNoneDescription": "This resource has its own policy.",
"resourceSharedPolicyOwnDescription": "This resource has its own authentication and access rules controls.", "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>.", "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>.", "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", "resourceUsersRoles": "Access Controls",

View File

@@ -44,6 +44,7 @@ import m38 from "./scriptsSqlite/1.18.0";
import m39 from "./scriptsSqlite/1.18.3"; import m39 from "./scriptsSqlite/1.18.3";
import m40 from "./scriptsSqlite/1.18.4"; import m40 from "./scriptsSqlite/1.18.4";
import m41 from "./scriptsSqlite/1.19.0"; import m41 from "./scriptsSqlite/1.19.0";
import m42 from "./scriptsSqlite/1.19.1";
// THIS CANNOT IMPORT ANYTHING FROM THE SERVER // THIS CANNOT IMPORT ANYTHING FROM THE SERVER
// EXCEPT FOR THE DATABASE AND THE SCHEMA // EXCEPT FOR THE DATABASE AND THE SCHEMA
@@ -85,7 +86,8 @@ const migrations = [
{ version: "1.18.0", run: m38 }, { version: "1.18.0", run: m38 },
{ version: "1.18.3", run: m39 }, { version: "1.18.3", run: m39 },
{ version: "1.18.4", run: m40 }, { version: "1.18.4", run: m40 },
{ version: "1.19.0", run: m41 } { version: "1.19.0", run: m41 },
{ version: "1.19.1", run: m42 }
// Add new migrations here as they are created // Add new migrations here as they are created
] as const; ] as const;

View File

@@ -228,7 +228,7 @@ export default async function migration() {
).run(); ).run();
db.prepare( db.prepare(
` `
UPDATE 'siteResources' SET 'destination2' = 'destination'; UPDATE 'siteResources' SET "destination2" = "destination";
` `
).run(); ).run();
db.prepare( db.prepare(
@@ -349,9 +349,9 @@ export default async function migration() {
db.prepare( db.prepare(
` `
UPDATE 'targets' UPDATE 'targets'
SET 'mode' = ( SET "mode" = (
SELECT 'mode' FROM 'resources' SELECT "mode" FROM 'resources'
WHERE 'resources'.'resourceId' = 'targets'.'resourceId' WHERE "resources"."resourceId" = "targets"."resourceId"
); );
` `
).run(); ).run();
@@ -680,6 +680,16 @@ export default async function migration() {
deleteResourceRules.run(resource.resourceId); deleteResourceRules.run(resource.resourceId);
deleteResourceWhitelist.run(resource.resourceId); deleteResourceWhitelist.run(resource.resourceId);
} }
});
migrateInlinePolicies();
console.log(
`Migrated inline resource policies for ${existingResources.length} resource(s)`
);
}
// add one more transaction
db.transaction(() => {
// remove not null/default from sso, applyRules, and emailWhitelistEnabled in preparation for resource policies // remove not null/default from sso, applyRules, and emailWhitelistEnabled in preparation for resource policies
db.prepare(`ALTER TABLE 'resources' DROP COLUMN 'sso';`).run(); db.prepare(`ALTER TABLE 'resources' DROP COLUMN 'sso';`).run();
db.prepare( db.prepare(
@@ -699,13 +709,7 @@ export default async function migration() {
db.prepare( db.prepare(
`ALTER TABLE 'resources' ADD COLUMN 'emailWhitelistEnabled' integer;` `ALTER TABLE 'resources' ADD COLUMN 'emailWhitelistEnabled' integer;`
).run(); ).run();
}); })();
migrateInlinePolicies();
console.log(
`Migrated inline resource policies for ${existingResources.length} resource(s)`
);
}
console.log("Migrated database"); console.log("Migrated database");
} catch (e) { } catch (e) {

View File

@@ -0,0 +1,59 @@
import { APP_PATH, __DIRNAME } from "@server/lib/consts";
import Database from "better-sqlite3";
import path from "path";
const version = "1.19.1";
export default async function migration() {
console.log(`Running setup script ${version}...`);
const location = path.join(APP_PATH, "db", "db.sqlite");
const db = new Database(location);
try {
db.transaction(() => {
// remove not null/default from sso, applyRules, and emailWhitelistEnabled in preparation for resource policies
db.prepare(
`ALTER TABLE 'resources' ADD COLUMN 'sso2' integer;`
).run();
db.prepare(`UPDATE 'resources' SET "sso2" = "sso";`).run();
db.prepare(`ALTER TABLE 'resources' DROP COLUMN 'sso';`).run();
db.prepare(
`ALTER TABLE 'resources' RENAME COLUMN 'sso2' TO 'sso';`
).run();
db.prepare(
`ALTER TABLE 'resources' ADD COLUMN 'applyRules2' integer;`
).run();
db.prepare(
`UPDATE 'resources' SET "applyRules2" = "applyRules";`
).run();
db.prepare(
`ALTER TABLE 'resources' DROP COLUMN 'applyRules';`
).run();
db.prepare(
`ALTER TABLE 'resources' RENAME COLUMN 'applyRules2' TO 'applyRules';`
).run();
db.prepare(
`ALTER TABLE 'resources' ADD COLUMN 'emailWhitelistEnabled2' integer;`
).run();
db.prepare(
`UPDATE 'resources' SET "emailWhitelistEnabled2" = "emailWhitelistEnabled";`
).run();
db.prepare(
`ALTER TABLE 'resources' DROP COLUMN 'emailWhitelistEnabled';`
).run();
db.prepare(
`ALTER TABLE 'resources' RENAME COLUMN 'emailWhitelistEnabled2' TO 'emailWhitelistEnabled';`
).run();
})();
console.log("Migrated database");
} catch (e) {
console.log("Failed to migrate db:", e);
throw e;
}
console.log(`${version} migration complete`);
}

View File

@@ -3,7 +3,9 @@ import { authCookieHeader } from "@app/lib/api/cookies";
import { ListOrgLabelsResponse } from "@server/routers/labels/types"; import { ListOrgLabelsResponse } from "@server/routers/labels/types";
import { AxiosResponse } from "axios"; import { AxiosResponse } from "axios";
import OrgLabelsTable from "@app/components/OrgLabelsTable"; import OrgLabelsTable from "@app/components/OrgLabelsTable";
import { PaidFeaturesAlert } from "@app/components/PaidFeaturesAlert";
import SettingsSectionTitle from "@app/components/SettingsSectionTitle"; import SettingsSectionTitle from "@app/components/SettingsSectionTitle";
import { tierMatrix } from "@server/lib/billing/tierMatrix";
import type { Metadata } from "next"; import type { Metadata } from "next";
import { getTranslations } from "next-intl/server"; import { getTranslations } from "next-intl/server";
@@ -49,6 +51,8 @@ export default async function LabelsPage({ params, searchParams }: Props) {
description={t("orgLabelsDescription")} description={t("orgLabelsDescription")}
/> />
<PaidFeaturesAlert tiers={tierMatrix.labels} />
<OrgLabelsTable <OrgLabelsTable
labels={labels} labels={labels}
orgId={orgId} orgId={orgId}

View File

@@ -43,6 +43,7 @@ import { usePaidStatus } from "@app/hooks/usePaidStatus";
import { tierMatrix, TierFeature } from "@server/lib/billing/tierMatrix"; import { tierMatrix, TierFeature } from "@server/lib/billing/tierMatrix";
import { PaidFeaturesAlert } from "@app/components/PaidFeaturesAlert"; import { PaidFeaturesAlert } from "@app/components/PaidFeaturesAlert";
import { ExternalLink } from "lucide-react"; import { ExternalLink } from "lucide-react";
import { env } from "process";
// Schema for general organization settings // Schema for general organization settings
const GeneralFormSchema = z.object({ const GeneralFormSchema = z.object({
@@ -165,6 +166,7 @@ function DeleteForm({ org }: SectionFormProps) {
function GeneralSectionForm({ org }: SectionFormProps) { function GeneralSectionForm({ org }: SectionFormProps) {
const { updateOrg } = useOrgContext(); const { updateOrg } = useOrgContext();
const { env } = useEnvContext();
const form = useForm({ const form = useForm({
resolver: zodResolver( resolver: zodResolver(
GeneralFormSchema.pick({ GeneralFormSchema.pick({
@@ -265,6 +267,7 @@ function GeneralSectionForm({ org }: SectionFormProps) {
<PaidFeaturesAlert <PaidFeaturesAlert
tiers={tierMatrix.newtAutoUpdate} tiers={tierMatrix.newtAutoUpdate}
/> />
{!env.flags.disableEnterpriseFeatures && (
<FormField <FormField
control={form.control} control={form.control}
name="settingsEnableGlobalNewtAutoUpdate" name="settingsEnableGlobalNewtAutoUpdate"
@@ -275,8 +278,12 @@ function GeneralSectionForm({ org }: SectionFormProps) {
id="settings-enable-global-newt-auto-update" id="settings-enable-global-newt-auto-update"
label={t("newtAutoUpdate")} label={t("newtAutoUpdate")}
checked={field.value} checked={field.value}
onCheckedChange={field.onChange} onCheckedChange={
disabled={!hasAutoUpdateFeature} field.onChange
}
disabled={
!hasAutoUpdateFeature
}
/> />
</FormControl> </FormControl>
<FormDescription> <FormDescription>
@@ -295,6 +302,7 @@ function GeneralSectionForm({ org }: SectionFormProps) {
</FormItem> </FormItem>
)} )}
/> />
)}
</form> </form>
</Form> </Form>
</SettingsSectionForm> </SettingsSectionForm>

View File

@@ -19,14 +19,14 @@ import {
SettingsSectionBody, SettingsSectionBody,
SettingsSectionDescription, SettingsSectionDescription,
SettingsSectionFooter, SettingsSectionFooter,
SettingsFormCell,
SettingsFormGrid, SettingsFormGrid,
SettingsSectionForm, SettingsSectionForm,
SettingsSectionHeader, SettingsSectionHeader,
SettingsSectionTitle, SettingsSectionTitle,
SettingsSubsectionDescription, SettingsSubsectionDescription,
SettingsSubsectionHeader, SettingsSubsectionHeader,
SettingsSubsectionTitle SettingsSubsectionTitle,
SettingsFormCell
} from "@app/components/Settings"; } from "@app/components/Settings";
import { SwitchInput } from "@app/components/SwitchInput"; import { SwitchInput } from "@app/components/SwitchInput";
import { useEnvContext } from "@app/hooks/useEnvContext"; import { useEnvContext } from "@app/hooks/useEnvContext";
@@ -70,7 +70,7 @@ export default function GeneralForm() {
const api = createApiClient({ env }); const api = createApiClient({ env });
const showResourcePolicy = const hasResourcePolicies =
build !== "oss" && build !== "oss" &&
isPaidUser(tierMatrix[TierFeature.ResourcePolicies]); isPaidUser(tierMatrix[TierFeature.ResourcePolicies]);
@@ -86,7 +86,7 @@ export default function GeneralForm() {
...orgQueries.resourcePolicy({ ...orgQueries.resourcePolicy({
resourcePolicyId: selectedSharedPolicyId! resourcePolicyId: selectedSharedPolicyId!
}), }),
enabled: showResourcePolicy && selectedSharedPolicyId !== null enabled: hasResourcePolicies && selectedSharedPolicyId !== null
}); });
const [resourceFullDomain, setResourceFullDomain] = useState( const [resourceFullDomain, setResourceFullDomain] = useState(
@@ -153,12 +153,11 @@ export default function GeneralForm() {
let resourcePolicyId: number | null | undefined; let resourcePolicyId: number | null | undefined;
if ( if (!["tcp", "udp"].includes(resource.mode)) {
showResourcePolicy && if (hasResourcePolicies || selectedSharedPolicyId === null) {
!["tcp", "udp"].includes(resource.mode)
) {
resourcePolicyId = selectedSharedPolicyId; resourcePolicyId = selectedSharedPolicyId;
} }
}
const res = await api const res = await api
.post<AxiosResponse<UpdateResourceResponse>>( .post<AxiosResponse<UpdateResourceResponse>>(
@@ -297,28 +296,6 @@ export default function GeneralForm() {
/> />
</SettingsFormCell> </SettingsFormCell>
<SettingsFormCell span="full">
<SettingsSubsectionHeader>
<SettingsSubsectionTitle>
{t(
"resourceGeneralDetailsSubsection"
)}
</SettingsSubsectionTitle>
<SettingsSubsectionDescription>
{t(
[
"tcp",
"udp",
].includes(
resource.mode
)
? "resourceGeneralDetailsSubsectionPortDescription"
: "resourceGeneralDetailsSubsectionDescription"
)}
</SettingsSubsectionDescription>
</SettingsSubsectionHeader>
</SettingsFormCell>
<SettingsFormCell span="half"> <SettingsFormCell span="half">
<FormField <FormField
control={form.control} control={form.control}
@@ -476,10 +453,9 @@ export default function GeneralForm() {
</div> </div>
</SettingsFormCell> </SettingsFormCell>
)} )}
{showResourcePolicy && { !["tcp", "udp"].includes(
!["tcp", "udp"].includes(
resource.mode resource.mode
) && ( ) && !env.flags.disableEnterpriseFeatures && (
<> <>
<SettingsFormCell span="full"> <SettingsFormCell span="full">
<SettingsSubsectionHeader> <SettingsSubsectionHeader>

View File

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

View File

@@ -253,7 +253,9 @@ export default function GeneralPage() {
<PaidFeaturesAlert <PaidFeaturesAlert
tiers={tierMatrix.newtAutoUpdate} tiers={tierMatrix.newtAutoUpdate}
/> />
{site && site.type === "newt" && ( {site &&
site.type === "newt" &&
!env.flags.disableEnterpriseFeatures && (
<FormField <FormField
control={form.control} control={form.control}
name="autoUpdateEnabled" name="autoUpdateEnabled"

View File

@@ -23,7 +23,7 @@ import {
} from "@app/components/ui/form"; } from "@app/components/ui/form";
import HeaderTitle from "@app/components/SettingsSectionTitle"; import HeaderTitle from "@app/components/SettingsSectionTitle";
import { z } from "zod"; import { z } from "zod";
import { createElement, useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { useForm } from "react-hook-form"; import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod"; import { zodResolver } from "@hookform/resolvers/zod";
import { Input } from "@app/components/ui/input"; import { Input } from "@app/components/ui/input";
@@ -37,15 +37,6 @@ import {
InfoSections, InfoSections,
InfoSectionTitle InfoSectionTitle
} from "@app/components/InfoSection"; } 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 { Alert, AlertDescription, AlertTitle } from "@app/components/ui/alert";
import { generateKeypair } from "../[niceId]/wireguardConfig"; import { generateKeypair } from "../[niceId]/wireguardConfig";
import { createApiClient, formatAxiosError } from "@app/lib/api"; import { createApiClient, formatAxiosError } from "@app/lib/api";
@@ -570,7 +561,7 @@ export default function Page() {
</Button> </Button>
</SettingsFormCell> </SettingsFormCell>
{showAdvancedSettings && ( {showAdvancedSettings && (
<SettingsFormCell span="quarter"> <SettingsFormCell span="half">
<FormField <FormField
control={ control={
form.control form.control

View File

@@ -156,10 +156,11 @@ export const orgNavSections = (
] ]
: []), : []),
// PaidFeaturesAlert // PaidFeaturesAlert
...((build === "oss" && !env?.flags.disableEnterpriseFeatures) || ...(!env?.flags.disableEnterpriseFeatures &&
build === "saas" || (build === "saas" ||
env?.app.identityProviderMode === "org" || env?.app.identityProviderMode === "org" ||
(env?.app.identityProviderMode === undefined && build !== "oss") (env?.app.identityProviderMode === undefined &&
build !== "oss"))
? [ ? [
{ {
title: "sidebarIdentityProviders", title: "sidebarIdentityProviders",
@@ -259,7 +260,7 @@ export const orgNavSections = (
href: "/{orgId}/settings/api-keys", href: "/{orgId}/settings/api-keys",
icon: <KeyRound className="size-4 flex-none" /> icon: <KeyRound className="size-4 flex-none" />
}, },
...(build !== "oss" ...(!env?.flags.disableEnterpriseFeatures
? [ ? [
{ {
title: "labels", title: "labels",

View File

@@ -12,14 +12,7 @@ import { useNavigationContext } from "@app/hooks/useNavigationContext";
import { toast } from "@app/hooks/useToast"; import { toast } from "@app/hooks/useToast";
import { createApiClient, formatAxiosError } from "@app/lib/api"; import { createApiClient, formatAxiosError } from "@app/lib/api";
import { type PaginationState } from "@tanstack/react-table"; import { type PaginationState } from "@tanstack/react-table";
import { import { ArrowRight, MoreHorizontal } from "lucide-react";
ArrowDown01Icon,
ArrowUp10Icon,
ChevronsUpDownIcon,
MoreHorizontal,
PencilIcon,
PencilLineIcon
} from "lucide-react";
import { useTranslations } from "next-intl"; import { useTranslations } from "next-intl";
import { usePathname, useRouter } from "next/navigation"; import { usePathname, useRouter } from "next/navigation";
import { useActionState, useMemo, useState, useTransition } from "react"; import { useActionState, useMemo, useState, useTransition } from "react";
@@ -109,7 +102,7 @@ export default function OrgLabelsTable({
cell: ({ row }) => ( cell: ({ row }) => (
<div className="flex items-center gap-1.5 group"> <div className="flex items-center gap-1.5 group">
<div <div
className="size-2.5 rounded-full bg-(--color) flex-none" className="size-2 rounded-full bg-(--color) flex-none"
style={{ style={{
// @ts-expect-error css color // @ts-expect-error css color
"--color": row.original.color "--color": row.original.color
@@ -125,22 +118,17 @@ export default function OrgLabelsTable({
enableHiding: false, enableHiding: false,
header: () => <span className="p-3"></span>, header: () => <span className="p-3"></span>,
cell: ({ row }) => ( cell: ({ row }) => (
<div className="flex items-center gap-2 justify-end">
<DropdownMenu> <DropdownMenu>
<DropdownMenuTrigger asChild> <DropdownMenuTrigger asChild>
<Button variant="ghost" className="h-8 w-8 p-0"> <Button variant="ghost" className="h-8 w-8 p-0">
<span className="sr-only">{t("openMenu")}</span> <span className="sr-only">
{t("openMenu")}
</span>
<MoreHorizontal className="h-4 w-4" /> <MoreHorizontal className="h-4 w-4" />
</Button> </Button>
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent align="end"> <DropdownMenuContent align="end">
<DropdownMenuItem
onClick={() => {
setSelectedLabel(row.original);
setIsEditModalOpen(true);
}}
>
{t("edit")}
</DropdownMenuItem>
<DropdownMenuItem <DropdownMenuItem
onClick={() => { onClick={() => {
setSelectedLabel(row.original); setSelectedLabel(row.original);
@@ -153,6 +141,17 @@ export default function OrgLabelsTable({
</DropdownMenuItem> </DropdownMenuItem>
</DropdownMenuContent> </DropdownMenuContent>
</DropdownMenu> </DropdownMenu>
<Button
variant="outline"
onClick={() => {
setSelectedLabel(row.original);
setIsEditModalOpen(true);
}}
>
{t("edit")}
<ArrowRight className="ml-2 w-4 h-4" />
</Button>
</div>
) )
} }
], ],

View File

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

View File

@@ -73,7 +73,9 @@ export function EditPolicyForm({
} }
const policyTiers = tierMatrix[TierFeature.ResourcePolicies]; const policyTiers = tierMatrix[TierFeature.ResourcePolicies];
const isDisabled = !isPaidUser(policyTiers); const isInlinePolicy = hidePolicyNameForm && resourceId === undefined;
const showPaidAlert = !isInlinePolicy;
const isDisabled = showPaidAlert && !isPaidUser(policyTiers);
const effectiveReadonly = readonly || isDisabled; const effectiveReadonly = readonly || isDisabled;
const authSection = ( const authSection = (
@@ -100,7 +102,7 @@ export function EditPolicyForm({
if (section === "general") { if (section === "general") {
return ( return (
<> <>
<PaidFeaturesAlert tiers={policyTiers} /> {showPaidAlert && <PaidFeaturesAlert tiers={policyTiers} />}
<div <div
className={ className={
isDisabled isDisabled
@@ -117,7 +119,7 @@ export function EditPolicyForm({
if (section === "authentication") { if (section === "authentication") {
return ( return (
<> <>
<PaidFeaturesAlert tiers={policyTiers} /> {showPaidAlert && <PaidFeaturesAlert tiers={policyTiers} />}
<div <div
className={ className={
isDisabled isDisabled
@@ -134,7 +136,7 @@ export function EditPolicyForm({
if (section === "rules") { if (section === "rules") {
return ( return (
<> <>
<PaidFeaturesAlert tiers={policyTiers} /> {showPaidAlert && <PaidFeaturesAlert tiers={policyTiers} />}
<div <div
className={ className={
isDisabled isDisabled
@@ -150,7 +152,7 @@ export function EditPolicyForm({
return ( return (
<> <>
<PaidFeaturesAlert tiers={policyTiers} /> {showPaidAlert && <PaidFeaturesAlert tiers={policyTiers} />}
<div <div
className={ className={
isDisabled ? "pointer-events-none opacity-50" : undefined isDisabled ? "pointer-events-none opacity-50" : undefined