Fix type in SelectedSite type and filter newts only on ssh creation

This commit is contained in:
Owen
2026-09-24 09:47:26 -04:00
parent 9aa2adadf6
commit 1dace9c9f7
16 changed files with 59 additions and 64 deletions
@@ -24,7 +24,7 @@ import { useActionState, useMemo, useState } from "react";
import { useForm } from "react-hook-form";
import { z } from "zod";
import { PrivateResourceSshFields } from "@app/components/PrivateResourceSshFields";
import type { Selectedsite } from "@app/components/site-selector";
import type { SelectedSite } from "@app/components/site-selector";
import { useSaveSiteResource } from "@app/hooks/useSaveSiteResource";
import {
asAnyControl,
@@ -84,7 +84,7 @@ export default function PrivateResourceSshPage() {
setSelectedSites(first);
form.setValue(
"siteIds",
first.map((s: Selectedsite) => s.siteId),
first.map((s: SelectedSite) => s.siteId),
{ shouldValidate: true }
);
}
@@ -27,7 +27,7 @@ import {
FormMessage
} from "@app/components/ui/form";
import { Input } from "@app/components/ui/input";
import type { Selectedsite } from "@app/components/site-selector";
import type { SelectedSite } from "@app/components/site-selector";
import { useEnvContext } from "@app/hooks/useEnvContext";
import { toast } from "@app/hooks/useToast";
import { createApiClient, formatAxiosError } from "@app/lib/api";
@@ -83,7 +83,7 @@ export default function CreatePrivateResourcePage() {
? Number(siteIdParam)
: null;
const [selectedSites, setSelectedSites] = useState<Selectedsite[]>([]);
const [selectedSites, setSelectedSites] = useState<SelectedSite[]>([]);
const [selectedProviders, setSelectedProviders] = useState<
SelectedAiProvider[]
>([]);
@@ -124,10 +124,10 @@ export default function CreatePrivateResourcePage() {
.then((res) => {
const site = res.data.data;
if (!site || site.orgId !== orgId) return;
const selected: Selectedsite = {
const selected: SelectedSite = {
siteId: site.siteId,
name: site.name,
type: site.type as Selectedsite["type"]
type: site.type as SelectedSite["type"]
};
setSelectedSites([selected]);
form.setValue("siteIds", [site.siteId]);
@@ -28,7 +28,7 @@ import {
import { BrowserGatewayTargetForm } from "@app/components/BrowserGatewayTargetForm";
import {
SitesSelector,
type Selectedsite
type SelectedSite
} from "@app/components/site-selector";
import { Button } from "@app/components/ui/button";
import {
@@ -253,7 +253,7 @@ export default function Page() {
"site" | "remote"
>("site");
const [nativeSelectedSite, setNativeSelectedSite] =
useState<Selectedsite | null>(null);
useState<SelectedSite | null>(null);
const [nativeSiteOpen, setNativeSiteOpen] = useState(false);
useEffect(() => {
@@ -1226,6 +1226,7 @@ export default function Page() {
</PopoverTrigger>
<PopoverContent className="w-[var(--radix-popover-trigger-width)] p-0">
<SitesSelector
filterTypes={["newt"]}
orgId={
orgId as string
}
+10 -14
View File
@@ -10,7 +10,7 @@ import {
MultiSitesSelector,
formatMultiSitesSelectorLabel
} from "./multi-site-selector";
import { SitesSelector, type Selectedsite } from "./site-selector";
import { SitesSelector, type SelectedSite } from "./site-selector";
import { Button } from "./ui/button";
import {
FormControl,
@@ -42,8 +42,7 @@ type SingleSiteFormProps<T extends FieldValues> = BaseProps<T> & {
};
export type BrowserGatewayTargetFormProps<T extends FieldValues = FieldValues> =
| MultiSiteFormProps<T>
| SingleSiteFormProps<T>;
MultiSiteFormProps<T> | SingleSiteFormProps<T>;
export function BrowserGatewayTargetForm<T extends FieldValues>(
props: BrowserGatewayTargetFormProps<T>
@@ -90,7 +89,7 @@ export function BrowserGatewayTargetForm<T extends FieldValues>(
const showMultiSiteDisclaimer =
props.multiSite === true &&
((watchedSites as Selectedsite[] | undefined)?.length ?? 0) > 1;
((watchedSites as SelectedSite[] | undefined)?.length ?? 0) > 1;
return (
<div className="space-y-2">
@@ -112,7 +111,7 @@ export function BrowserGatewayTargetForm<T extends FieldValues>(
"aria-invalid:border-destructive aria-invalid:ring-destructive/20",
props.multiSite === true
? (
field.value as Selectedsite[]
field.value as SelectedSite[]
)?.length === 0 &&
"text-muted-foreground"
: !field.value &&
@@ -122,12 +121,12 @@ export function BrowserGatewayTargetForm<T extends FieldValues>(
<span className="truncate">
{props.multiSite === true
? formatMultiSitesSelectorLabel(
(field.value as Selectedsite[]) ??
(field.value as SelectedSite[]) ??
[],
t
)
: ((
field.value as Selectedsite | null
field.value as SelectedSite | null
)?.name ??
t("siteSelect"))}
</span>
@@ -140,7 +139,7 @@ export function BrowserGatewayTargetForm<T extends FieldValues>(
<MultiSitesSelector
orgId={props.orgId}
selectedSites={
(field.value as Selectedsite[]) ??
(field.value as SelectedSite[]) ??
[]
}
onSelectionChange={field.onChange}
@@ -150,7 +149,7 @@ export function BrowserGatewayTargetForm<T extends FieldValues>(
<SitesSelector
orgId={props.orgId}
selectedSite={
field.value as Selectedsite | null
field.value as SelectedSite | null
}
onSelectSite={(site) => {
field.onChange(site);
@@ -179,8 +178,7 @@ export function BrowserGatewayTargetForm<T extends FieldValues>(
onChange={field.onChange}
value={
(watchedDestination as
| string
| undefined) ?? ""
string | undefined) ?? ""
}
/>
</FormControl>
@@ -205,9 +203,7 @@ export function BrowserGatewayTargetForm<T extends FieldValues>(
onChange={field.onChange}
value={
(watchedDestinationPort as
| string
| number
| undefined) ?? ""
string | number | undefined) ?? ""
}
/>
</FormControl>
+5 -7
View File
@@ -46,7 +46,7 @@ import {
PopoverTrigger
} from "@/components/ui/popover";
import { SitesSelector } from "@app/components/site-selector";
import type { Selectedsite } from "@app/components/site-selector";
import type { SelectedSite } from "@app/components/site-selector";
import { CaretSortIcon } from "@radix-ui/react-icons";
import { cn } from "@app/lib/cn";
import { SwitchInput } from "@app/components/SwitchInput";
@@ -144,7 +144,7 @@ export function HealthCheckCredenza(props: HealthCheckCredenzaProps) {
const t = useTranslations();
const api = createApiClient(useEnvContext());
const [loading, setLoading] = useState(false);
const [selectedSite, setSelectedSite] = useState<Selectedsite | null>(null);
const [selectedSite, setSelectedSite] = useState<SelectedSite | null>(null);
const healthCheckSchema = z
.object({
@@ -183,11 +183,9 @@ export function HealthCheckCredenza(props: HealthCheckCredenzaProps) {
{ message: t("healthCheckPortInvalid") }
),
hcFollowRedirects: z.boolean(),
hcHostname: z
.string()
.refine((val) => !/\s/.test(val), {
message: t("healthCheckHostnameInvalid")
}),
hcHostname: z.string().refine((val) => !/\s/.test(val), {
message: t("healthCheckHostnameInvalid")
}),
hcMode: z.string(),
hcUnhealthyInterval: z.int().positive().min(5),
hcTlsServerName: z.string(),
+4 -4
View File
@@ -28,7 +28,7 @@ import { Switch } from "@app/components/ui/switch";
import { toast } from "@app/hooks/useToast";
import { useEnvContext } from "@app/hooks/useEnvContext";
import { createApiClient, formatAxiosError } from "@app/lib/api";
import { Selectedsite, SitesSelector } from "@app/components/site-selector";
import { SelectedSite, SitesSelector } from "@app/components/site-selector";
import {
ResourceSelector,
SelectedResource
@@ -59,7 +59,7 @@ type StandaloneHealthChecksTableProps = {
healthChecks: HealthCheckRow[];
rowCount: number;
pagination: PaginationState;
initialFilterSite?: Selectedsite | null;
initialFilterSite?: SelectedSite | null;
initialFilterResource?: SelectedResource | null;
};
@@ -117,7 +117,7 @@ export default function HealthChecksTable({
const siteIdQ = searchParams.get("siteId");
const siteIdNum = siteIdQ ? parseInt(siteIdQ, 10) : NaN;
const selectedSite: Selectedsite | null = useMemo(() => {
const selectedSite: SelectedSite | null = useMemo(() => {
if (!siteIdQ || !Number.isInteger(siteIdNum) || siteIdNum <= 0) {
return null;
}
@@ -227,7 +227,7 @@ export default function HealthChecksTable({
setResourceFilterOpen(false);
};
const onPickSite = (site: Selectedsite) => {
const onPickSite = (site: SelectedSite) => {
handleFilterChange("siteId", String(site.siteId));
setSiteFilterOpen(false);
};
+3 -3
View File
@@ -5,7 +5,7 @@ import {
formatMultiSitesSelectorLabel
} from "@app/components/multi-site-selector";
import { SitesSelector } from "@app/components/site-selector";
import type { Selectedsite } from "@app/components/site-selector";
import type { SelectedSite } from "@app/components/site-selector";
import { Button } from "@app/components/ui/button";
import {
FormControl,
@@ -28,8 +28,8 @@ import { PrivateResourceMultiSiteRoutingHelp } from "@app/components/PrivateReso
type PrivateResourceSitesFieldProps<T extends FieldValues> = {
control: Control<T>;
orgId: string;
selectedSites: Selectedsite[];
onSelectedSitesChange: (sites: Selectedsite[]) => void;
selectedSites: SelectedSite[];
onSelectedSitesChange: (sites: SelectedSite[]) => void;
siteIdsFieldName?: FieldPath<T>;
singleSite?: boolean;
};
+4 -4
View File
@@ -25,7 +25,7 @@ import { tierMatrix } from "@server/lib/billing/tierMatrix";
import { useTranslations } from "next-intl";
import { useState, type ReactNode } from "react";
import type { Control, UseFormSetValue, UseFormWatch } from "react-hook-form";
import type { Selectedsite } from "@app/components/site-selector";
import type { SelectedSite } from "@app/components/site-selector";
type PrivateResourceSshFieldsProps = {
control: Control<any>;
@@ -33,8 +33,8 @@ type PrivateResourceSshFieldsProps = {
watch: UseFormWatch<any>;
orgId?: string;
disabled?: boolean;
selectedSites: Selectedsite[];
onSelectedSitesChange: (sites: Selectedsite[]) => void;
selectedSites: SelectedSite[];
onSelectedSitesChange: (sites: SelectedSite[]) => void;
labelPrefix?: "create" | "edit";
showSshSettings?: boolean;
layout?: "default" | "wizard";
@@ -101,7 +101,7 @@ export function PrivateResourceSshFields({
onSelectedSitesChange(first);
setValue(
"siteIds",
first.map((s: Selectedsite) => s.siteId),
first.map((s: SelectedSite) => s.siteId),
{ shouldValidate: true }
);
}
+1 -1
View File
@@ -7,7 +7,7 @@ import {
ResourceSitesStatusCell,
type ResourceSiteRow
} from "@app/components/ResourceSitesStatusCell";
import { Selectedsite, SitesSelector } from "@app/components/site-selector";
import { SelectedSite, SitesSelector } from "@app/components/site-selector";
import { Badge } from "@app/components/ui/badge";
import { Button } from "@app/components/ui/button";
import { ExtendedColumnDef } from "@app/components/ui/data-table";
+2 -2
View File
@@ -7,7 +7,7 @@ import {
ResourceSitesStatusCell,
type ResourceSiteRow
} from "@app/components/ResourceSitesStatusCell";
import { Selectedsite } from "@app/components/site-selector";
import { SelectedSite } from "@app/components/site-selector";
import { Button } from "@app/components/ui/button";
import { ExtendedColumnDef } from "@app/components/ui/data-table";
import {
@@ -105,7 +105,7 @@ type ProxyResourcesTableProps = {
orgId: string;
pagination: PaginationState;
rowCount: number;
initialFilterSite?: Selectedsite | null;
initialFilterSite?: SelectedSite | null;
/** Certificates prefetched on the server, keyed by full domain. */
initialCertificates?: GetBatchedCertificateResponse;
};
+2 -2
View File
@@ -3,7 +3,7 @@ import { Popover, PopoverContent, PopoverTrigger } from "./ui/popover";
import { cn } from "@app/lib/cn";
import { dataTableFilterPopoverContentClassName } from "@app/lib/dataTableFilterPopover";
import { CheckIcon, Funnel } from "lucide-react";
import { SiteOnlineStatus, type Selectedsite } from "./site-selector";
import { SiteOnlineStatus, type SelectedSite } from "./site-selector";
import { Button } from "./ui/button";
import { useTranslations } from "next-intl";
import { Badge } from "./ui/badge";
@@ -62,7 +62,7 @@ export function SitesColumnFilterButton({
// always include the selected site in the list of sites shown
const sitesShown = useMemo(() => {
const allSites: Array<Selectedsite> = [...sites];
const allSites: Array<SelectedSite> = [...sites];
if (
debouncedQuery.trim().length === 0 &&
selectedSite &&
+5 -5
View File
@@ -12,12 +12,12 @@ import {
import { Checkbox } from "./ui/checkbox";
import { useTranslations } from "next-intl";
import { useDebounce } from "use-debounce";
import { SiteOnlineStatus, type Selectedsite } from "./site-selector";
import { SiteOnlineStatus, type SelectedSite } from "./site-selector";
export type MultiSitesSelectorProps = {
orgId: string;
selectedSites: Selectedsite[];
onSelectionChange: (sites: Selectedsite[]) => void;
selectedSites: SelectedSite[];
onSelectionChange: (sites: SelectedSite[]) => void;
filterTypes?: string[];
scope?: "org" | "launcher";
onClear?: () => void;
@@ -25,7 +25,7 @@ export type MultiSitesSelectorProps = {
};
export function formatMultiSitesSelectorLabel(
selectedSites: Selectedsite[],
selectedSites: SelectedSite[],
t: (key: string, values?: { count: number }) => string
): string {
if (selectedSites.length === 0) {
@@ -91,7 +91,7 @@ export function MultiSitesSelector({
[selectedSites]
);
const toggleSite = (site: Selectedsite) => {
const toggleSite = (site: SelectedSite) => {
if (selectedIds.has(site.siteId)) {
onSelectionChange(
selectedSites.filter((s) => s.siteId !== site.siteId)
@@ -23,13 +23,13 @@ import { useQuery } from "@tanstack/react-query";
import { useTranslations } from "next-intl";
import { ChevronsUpDown, Funnel } from "lucide-react";
import { useMemo, useState } from "react";
import type { Selectedsite } from "@app/components/site-selector";
import type { SelectedSite } from "@app/components/site-selector";
type LauncherFilterPopoverProps = {
orgId: string;
selectedSites: Selectedsite[];
selectedSites: SelectedSite[];
selectedLabels: SelectedLabel[];
onSitesChange: (sites: Selectedsite[]) => void;
onSitesChange: (sites: SelectedSite[]) => void;
onLabelsChange: (labels: SelectedLabel[]) => void;
};
@@ -58,7 +58,7 @@ export function LauncherFilterPopover({
})
);
const resolvedSelectedSites: Selectedsite[] = useMemo(
const resolvedSelectedSites: SelectedSite[] = useMemo(
() =>
selectedSites.map((selected) => {
const found = sites.find(
@@ -63,7 +63,7 @@ import {
useTransition
} from "react";
import { useDebouncedCallback } from "use-debounce";
import type { Selectedsite } from "@app/components/site-selector";
import type { SelectedSite } from "@app/components/site-selector";
import type { SelectedLabel } from "@app/components/labels-selector";
import { useMediaQuery } from "@app/hooks/useMediaQuery";
import { cn } from "@app/lib/cn";
@@ -256,7 +256,7 @@ export default function ResourceLauncher({
(Boolean(defaultViewOverrides.personal) ||
(isAdmin && Boolean(defaultViewOverrides.orgWide)));
const selectedSites: Selectedsite[] = useMemo(
const selectedSites: SelectedSite[] = useMemo(
() =>
config.siteIds.map((siteId) => ({
siteId,
+6 -6
View File
@@ -15,7 +15,7 @@ import { CheckIcon } from "lucide-react";
import { useTranslations } from "next-intl";
import { useDebounce } from "use-debounce";
export type Selectedsite = Pick<
export type SelectedSite = Pick<
ListSitesResponse["sites"][number],
"name" | "siteId" | "type"
> & {
@@ -24,8 +24,8 @@ export type Selectedsite = Pick<
};
type SiteOnlineStatusProps = {
type: Selectedsite["type"];
online: Selectedsite["online"];
type: SelectedSite["type"];
online: SelectedSite["online"];
};
/** Dot-only indicator matching `SitesTable` colors (newt/wireguard only; nothing for local or missing status). */
@@ -57,8 +57,8 @@ export function SiteOnlineStatus({ type, online }: SiteOnlineStatusProps) {
export type SitesSelectorProps = {
orgId: string;
selectedSite?: Selectedsite | null;
onSelectSite: (selected: Selectedsite) => void;
selectedSite?: SelectedSite | null;
onSelectSite: (selected: SelectedSite) => void;
filterTypes?: string[];
};
@@ -82,7 +82,7 @@ export function SitesSelector({
// always include the selected site in the list of sites shown
const sitesShown = useMemo(() => {
const allSites: Array<Selectedsite> = filterTypes
const allSites: Array<SelectedSite> = filterTypes
? sites.filter((s) => filterTypes.includes(s.type))
: [...sites];
if (
+2 -2
View File
@@ -1,11 +1,11 @@
"use client";
import type { Selectedsite } from "@app/components/site-selector";
import type { SelectedSite } from "@app/components/site-selector";
import type { SiteResourceData } from "@app/lib/privateResourceForm";
export function buildSelectedSitesForResource(
resource: Pick<SiteResourceData, "siteIds" | "siteNames">
): Selectedsite[] {
): SelectedSite[] {
return resource.siteIds.map((siteId, idx) => ({
name: resource.siteNames[idx] ?? "",
siteId,