move cert to info box and add network access to create wizard

This commit is contained in:
miloschwartz
2026-07-07 14:51:15 -04:00
parent 4c8bd4db0a
commit beaa5735ce
8 changed files with 65 additions and 40 deletions
+1
View File
@@ -3585,6 +3585,7 @@
"memberPortalResourceDisabled": "Resource Disabled", "memberPortalResourceDisabled": "Resource Disabled",
"memberPortalShowingResources": "Showing {start}-{end} of {total} resources", "memberPortalShowingResources": "Showing {start}-{end} of {total} resources",
"resourceLauncherTitle": "Resource Launcher", "resourceLauncherTitle": "Resource Launcher",
"resourceSidebarLauncherTitle": "Launcher",
"resourceLauncherDescription": "View all available resources and launch them from one central hub", "resourceLauncherDescription": "View all available resources and launch them from one central hub",
"resourceLauncherSearchPlaceholder": "Search your resources...", "resourceLauncherSearchPlaceholder": "Search your resources...",
"resourceLauncherDefaultView": "Default", "resourceLauncherDefaultView": "Default",
@@ -1,6 +1,5 @@
"use client"; "use client";
import CertificateStatus from "@app/components/CertificateStatus";
import DomainPicker from "@app/components/DomainPicker"; import DomainPicker from "@app/components/DomainPicker";
import { PaidFeaturesAlert } from "@app/components/PaidFeaturesAlert"; import { PaidFeaturesAlert } from "@app/components/PaidFeaturesAlert";
import { import {
@@ -27,7 +26,6 @@ import {
SelectValue SelectValue
} from "@app/components/ui/select"; } from "@app/components/ui/select";
import { tierMatrix } from "@server/lib/billing/tierMatrix"; import { tierMatrix } from "@server/lib/billing/tierMatrix";
import { build } from "@server/build";
import { useTranslations } from "next-intl"; import { useTranslations } from "next-intl";
import type { Control, UseFormSetValue, UseFormWatch } from "react-hook-form"; import type { Control, UseFormSetValue, UseFormWatch } from "react-hook-form";
@@ -38,8 +36,6 @@ type PrivateResourceHttpFieldsProps = {
watch: UseFormWatch<any>; watch: UseFormWatch<any>;
disabled?: boolean; disabled?: boolean;
siteResourceId?: number; siteResourceId?: number;
resourceDomainId?: string | null;
resourceFullDomain?: string | null;
labelPrefix?: "create" | "edit"; labelPrefix?: "create" | "edit";
hideDomainPicker?: boolean; hideDomainPicker?: boolean;
hidePaidFeaturesAlert?: boolean; hidePaidFeaturesAlert?: boolean;
@@ -52,8 +48,6 @@ export function PrivateResourceHttpFields({
watch, watch,
disabled = false, disabled = false,
siteResourceId, siteResourceId,
resourceDomainId,
resourceFullDomain,
labelPrefix = "edit", labelPrefix = "edit",
hideDomainPicker = false, hideDomainPicker = false,
hidePaidFeaturesAlert = false hidePaidFeaturesAlert = false
@@ -91,7 +85,6 @@ export function PrivateResourceHttpFields({
const httpConfigSubdomain = watch("httpConfigSubdomain"); const httpConfigSubdomain = watch("httpConfigSubdomain");
const httpConfigDomainId = watch("httpConfigDomainId"); const httpConfigDomainId = watch("httpConfigDomainId");
const httpConfigFullDomain = watch("httpConfigFullDomain"); const httpConfigFullDomain = watch("httpConfigFullDomain");
const ssl = watch("ssl");
return ( return (
<SettingsFormGrid> <SettingsFormGrid>
@@ -274,29 +267,6 @@ export function PrivateResourceHttpFields({
)} )}
/> />
</SettingsFormCell> </SettingsFormCell>
{siteResourceId &&
resourceDomainId &&
httpConfigFullDomain &&
httpConfigDomainId === resourceDomainId &&
httpConfigFullDomain === resourceFullDomain &&
build != "oss" &&
ssl && (
<SettingsFormCell span="half">
<div className="flex items-center gap-2 pt-1">
<span className="text-sm font-medium">
{t("certificateStatus")}:
</span>
<CertificateStatus
orgId={orgId}
domainId={resourceDomainId}
fullDomain={httpConfigFullDomain}
autoFetch={true}
showLabel={false}
polling={true}
/>
</div>
</SettingsFormCell>
)}
</> </>
)} )}
@@ -122,12 +122,6 @@ export default function PrivateResourceHttpPage() {
watch={asAnyWatch(form.watch)} watch={asAnyWatch(form.watch)}
disabled={httpSectionDisabled} disabled={httpSectionDisabled}
siteResourceId={siteResource.id} siteResourceId={siteResource.id}
resourceDomainId={
siteResource.domainId
}
resourceFullDomain={
siteResource.fullDomain
}
/> />
</SettingsFormCell> </SettingsFormCell>
</SettingsFormGrid> </SettingsFormGrid>
@@ -53,6 +53,7 @@ import { z } from "zod";
import { PrivateResourceSitesField } from "../PrivateResourceSitesField"; import { PrivateResourceSitesField } from "../PrivateResourceSitesField";
import { PrivateResourceHttpFields } from "../PrivateResourceHttpFields"; import { PrivateResourceHttpFields } from "../PrivateResourceHttpFields";
import { PrivateResourceSshFields } from "../PrivateResourceSshFields"; import { PrivateResourceSshFields } from "../PrivateResourceSshFields";
import { PrivateResourcePortRanges } from "../PrivateResourcePortRanges";
import { import {
PrivateResourceAliasField, PrivateResourceAliasField,
PrivateResourceCidrDestinationField, PrivateResourceCidrDestinationField,
@@ -105,6 +106,8 @@ export default function CreatePrivateResourcePage() {
standardDaemonLocation: "site", standardDaemonLocation: "site",
authDaemonPort: null, authDaemonPort: null,
pamMode: "passthrough", pamMode: "passthrough",
tcpPortRangeString: "*",
udpPortRangeString: "*",
disableIcmp: false disableIcmp: false
} }
}); });
@@ -426,6 +429,16 @@ export default function CreatePrivateResourcePage() {
hideAlias hideAlias
/> />
</SettingsFormCell> </SettingsFormCell>
<SettingsFormCell span="full">
<PrivateResourcePortRanges
control={asAnyControl(
form.control
)}
setValue={asAnySetValue(
form.setValue
)}
/>
</SettingsFormCell>
</SettingsFormGrid> </SettingsFormGrid>
</SettingsSectionForm> </SettingsSectionForm>
</SettingsSectionBody> </SettingsSectionBody>
@@ -466,6 +479,16 @@ export default function CreatePrivateResourcePage() {
labelPrefix="create" labelPrefix="create"
/> />
</SettingsFormCell> </SettingsFormCell>
<SettingsFormCell span="full">
<PrivateResourcePortRanges
control={asAnyControl(
form.control
)}
setValue={asAnySetValue(
form.setValue
)}
/>
</SettingsFormCell>
</SettingsFormGrid> </SettingsFormGrid>
</SettingsSectionForm> </SettingsSectionForm>
</SettingsSectionBody> </SettingsSectionBody>
+1 -1
View File
@@ -155,7 +155,7 @@ export function LayoutMobileMenu({
</span> </span>
<span className="flex-1"> <span className="flex-1">
{t( {t(
"resourceLauncherTitle" "resourceSidebarLauncherTitle"
)} )}
</span> </span>
</Link> </Link>
+2 -2
View File
@@ -195,7 +195,7 @@ export function LayoutSidebar({
sideOffset={8} sideOffset={8}
> >
<p> <p>
{t("resourceLauncherTitle")} {t("resourceSidebarLauncherTitle")}
</p> </p>
</TooltipContent> </TooltipContent>
</Tooltip> </Tooltip>
@@ -211,7 +211,7 @@ export function LayoutSidebar({
<SquareMousePointer className="h-4 w-4" /> <SquareMousePointer className="h-4 w-4" />
</span> </span>
<span className="flex-1"> <span className="flex-1">
{t("resourceLauncherTitle")} {t("resourceSidebarLauncherTitle")}
</span> </span>
</Link> </Link>
)} )}
+36 -1
View File
@@ -1,5 +1,6 @@
"use client"; "use client";
import CertificateStatus from "@app/components/CertificateStatus";
import CopyToClipboard from "@app/components/CopyToClipboard"; import CopyToClipboard from "@app/components/CopyToClipboard";
import { import {
InfoSection, InfoSection,
@@ -17,14 +18,17 @@ import {
type LauncherAccessFields type LauncherAccessFields
} from "@app/lib/launcherResourceAccess"; } from "@app/lib/launcherResourceAccess";
import type { PrivateResourceMode } from "@app/lib/privateResourceForm"; import type { PrivateResourceMode } from "@app/lib/privateResourceForm";
import { build } from "@server/build";
import { useTranslations } from "next-intl"; import { useTranslations } from "next-intl";
type SiteResourceInfoInput = { type SiteResourceInfoInput = {
orgId: string;
mode: PrivateResourceMode; mode: PrivateResourceMode;
destination: string | null; destination: string | null;
destinationPort: number | null; destinationPort: number | null;
scheme: "http" | "https" | null; scheme: "http" | "https" | null;
ssl: boolean; ssl: boolean;
domainId?: string | null;
fullDomain?: string | null; fullDomain?: string | null;
alias?: string | null; alias?: string | null;
aliasAddress?: string | null; aliasAddress?: string | null;
@@ -108,9 +112,20 @@ export function SiteResourceInfoSections({
const showDestination = !( const showDestination = !(
siteResource.mode === "ssh" && siteResource.authDaemonMode === "native" siteResource.mode === "ssh" && siteResource.authDaemonMode === "native"
); );
const showCertificate = !!(
siteResource.mode === "http" &&
siteResource.ssl &&
siteResource.domainId &&
siteResource.fullDomain &&
build != "oss"
);
const numSections = const numSections =
2 + (showDestination ? 1 : 0) + (showAlias ? 1 : 0) + (isPanel ? 1 : 0); 2 +
(showDestination ? 1 : 0) +
(showAlias ? 1 : 0) +
(showCertificate ? 1 : 0) +
(isPanel ? 1 : 0);
const sections = ( const sections = (
<InfoSections cols={numSections} layout={isPanel ? "panel" : "default"}> <InfoSections cols={numSections} layout={isPanel ? "panel" : "default"}>
@@ -155,6 +170,26 @@ export function SiteResourceInfoSections({
</InfoSection> </InfoSection>
) : null} ) : null}
{showCertificate ? (
<InfoSection>
<InfoSectionTitle>
{t("certificateStatus", {
defaultValue: "Certificate"
})}
</InfoSectionTitle>
<InfoSectionContent>
<CertificateStatus
orgId={siteResource.orgId}
domainId={siteResource.domainId!}
fullDomain={siteResource.fullDomain!}
autoFetch={true}
showLabel={false}
polling={true}
/>
</InfoSectionContent>
</InfoSection>
) : null}
{isPanel ? ( {isPanel ? (
<InfoSection> <InfoSection>
<InfoSectionTitle>{t("portRestrictions")}</InfoSectionTitle> <InfoSectionTitle>{t("portRestrictions")}</InfoSectionTitle>
+2
View File
@@ -400,6 +400,8 @@ export function createCreateFormSchema(t: TranslateFn) {
.nullable(), .nullable(),
authDaemonPort: z.number().int().positive().optional().nullable(), authDaemonPort: z.number().int().positive().optional().nullable(),
pamMode: z.enum(["passthrough", "push"]).optional().nullable(), pamMode: z.enum(["passthrough", "push"]).optional().nullable(),
tcpPortRangeString: createPortRangeStringSchema(t),
udpPortRangeString: createPortRangeStringSchema(t),
disableIcmp: z.boolean().optional() disableIcmp: z.boolean().optional()
}) })
.superRefine((data, ctx) => { .superRefine((data, ctx) => {