Adjusting the create ui

This commit is contained in:
Owen
2026-05-26 16:10:06 -07:00
parent d47ad9ac40
commit 0d4bb65a92
4 changed files with 145 additions and 324 deletions

View File

@@ -240,6 +240,7 @@
"domainType": "Domain Type", "domainType": "Domain Type",
"subdomain": "Subdomain", "subdomain": "Subdomain",
"baseDomain": "Base Domain", "baseDomain": "Base Domain",
"configure": "Configure",
"subdomnainDescription": "The subdomain where the resource will be accessible.", "subdomnainDescription": "The subdomain where the resource will be accessible.",
"resourceRawSettings": "TCP/UDP Settings", "resourceRawSettings": "TCP/UDP Settings",
"resourceRawSettingsDescription": "Configure how the resource will be accessed over TCP/UDP", "resourceRawSettingsDescription": "Configure how the resource will be accessed over TCP/UDP",

View File

@@ -23,7 +23,10 @@ import { OpenAPITags, registry } from "@server/openApi";
import { build } from "@server/build"; import { build } from "@server/build";
import { createCertificate } from "#dynamic/routers/certificates/createCertificate"; import { createCertificate } from "#dynamic/routers/certificates/createCertificate";
import { getUniqueResourceName } from "@server/db/names"; import { getUniqueResourceName } from "@server/db/names";
import { validateAndConstructDomain, checkWildcardDomainConflict } from "@server/lib/domainUtils"; import {
validateAndConstructDomain,
checkWildcardDomainConflict
} from "@server/lib/domainUtils";
import { isSubscribed } from "#dynamic/lib/isSubscribed"; import { isSubscribed } from "#dynamic/lib/isSubscribed";
import { isLicensedOrSubscribed } from "#dynamic/lib/isLicencedOrSubscribed"; import { isLicensedOrSubscribed } from "#dynamic/lib/isLicencedOrSubscribed";
import { tierMatrix } from "@server/lib/billing/tierMatrix"; import { tierMatrix } from "@server/lib/billing/tierMatrix";
@@ -40,7 +43,8 @@ const createHttpResourceSchema = z
protocol: z.enum(["tcp", "udp"]), protocol: z.enum(["tcp", "udp"]),
domainId: z.string(), domainId: z.string(),
stickySession: z.boolean().optional(), stickySession: z.boolean().optional(),
postAuthPath: z.string().nullable().optional() postAuthPath: z.string().nullable().optional(),
browserAccessType: z.enum(["http", "ssh", "rdp", "vnc"]).optional()
}) })
.refine( .refine(
(data) => { (data) => {
@@ -198,7 +202,7 @@ async function createHttpResource(
); );
} }
const { name, domainId, postAuthPath } = parsedBody.data; const { name, domainId, postAuthPath, browserAccessType } = parsedBody.data;
const subdomain = parsedBody.data.subdomain; const subdomain = parsedBody.data.subdomain;
const stickySession = parsedBody.data.stickySession; const stickySession = parsedBody.data.stickySession;
@@ -323,6 +327,7 @@ async function createHttpResource(
name, name,
subdomain: finalSubdomain, subdomain: finalSubdomain,
http: true, http: true,
browserAccessType: browserAccessType,
protocol: "tcp", protocol: "tcp",
ssl: true, ssl: true,
stickySession: stickySession, stickySession: stickySession,

View File

@@ -121,10 +121,6 @@ export default function ReverseProxyTargetsPage(props: {
const params = use(props.params); const params = use(props.params);
const { resource, updateResource } = useResourceContext(); const { resource, updateResource } = useResourceContext();
const [targetMode, setTargetMode] = useState<
"http" | "ssh" | "rdp" | "vnc"
>((resource.browserAccessType as "http" | "ssh" | "rdp" | "vnc") || "http");
const { data: remoteTargets = [], isLoading: isLoadingTargets } = useQuery( const { data: remoteTargets = [], isLoading: isLoadingTargets } = useQuery(
resourceQueries.resourceTargets({ resourceQueries.resourceTargets({
resourceId: resource.resourceId resourceId: resource.resourceId
@@ -141,12 +137,10 @@ export default function ReverseProxyTargetsPage(props: {
orgId={params.orgId} orgId={params.orgId}
initialTargets={remoteTargets} initialTargets={remoteTargets}
resource={resource} resource={resource}
targetMode={targetMode}
setTargetMode={setTargetMode}
updateResource={updateResource} updateResource={updateResource}
/> />
{resource.http && targetMode === "http" && ( {resource.http && (
<ProxyResourceHttpForm <ProxyResourceHttpForm
resource={resource} resource={resource}
updateResource={updateResource} updateResource={updateResource}
@@ -167,15 +161,11 @@ function ProxyResourceTargetsForm({
orgId, orgId,
initialTargets, initialTargets,
resource, resource,
targetMode,
setTargetMode,
updateResource updateResource
}: { }: {
initialTargets: LocalTarget[]; initialTargets: LocalTarget[];
orgId: string; orgId: string;
resource: GetResourceResponse; resource: GetResourceResponse;
targetMode: "http" | "ssh" | "rdp" | "vnc";
setTargetMode: (mode: "http" | "ssh" | "rdp" | "vnc") => void;
updateResource: ResourceContextType["updateResource"]; updateResource: ResourceContextType["updateResource"];
}) { }) {
const t = useTranslations(); const t = useTranslations();
@@ -310,7 +300,6 @@ function ProxyResourceTargetsForm({
useEffect(() => { useEffect(() => {
if (!bgTargetsResponse?.targets?.length) return; if (!bgTargetsResponse?.targets?.length) return;
const bgt = bgTargetsResponse.targets[0]; const bgt = bgTargetsResponse.targets[0];
setTargetMode(bgt.type as "ssh" | "rdp" | "vnc");
setBgDestination(bgt.destination); setBgDestination(bgt.destination);
setBgDestinationPort(String(bgt.destinationPort)); setBgDestinationPort(String(bgt.destinationPort));
setBgSiteId(bgt.siteId); setBgSiteId(bgt.siteId);
@@ -781,55 +770,6 @@ function ProxyResourceTargetsForm({
const [, formAction, isSubmitting] = useActionState(saveTargets, null); const [, formAction, isSubmitting] = useActionState(saveTargets, null);
async function saveTargets() { async function saveTargets() {
if (targetMode !== "http") {
try {
if (!bgDestination || !bgDestinationPort) {
if (bgTargetId) {
await api.delete(
`/org/${orgId}/browser-gateway-target/${bgTargetId}`
);
setBgTargetId(null);
}
} else if (bgTargetId) {
await api.post(
`/org/${orgId}/browser-gateway-target/${bgTargetId}`,
{
type: targetMode,
destination: bgDestination,
destinationPort: Number(bgDestinationPort),
siteId: bgSiteId
}
);
} else {
const res = await api.put(
`/org/${orgId}/resource/${resource.resourceId}/browser-gateway-target`,
{
siteId: bgSiteId ?? sites[0]?.siteId,
type: targetMode,
destination: bgDestination,
destinationPort: Number(bgDestinationPort)
}
);
setBgTargetId(res.data.data.browserGatewayTargetId);
}
toast({
title: t("settingsUpdated"),
description: t("settingsUpdatedDescription")
});
} catch (err) {
console.error(err);
toast({
variant: "destructive",
title: t("settingsErrorUpdate"),
description: formatAxiosError(
err,
t("settingsErrorUpdateDescription")
)
});
}
return;
}
// Validate that no targets have blank IPs or invalid ports // Validate that no targets have blank IPs or invalid ports
const targetsWithInvalidFields = targets.filter( const targetsWithInvalidFields = targets.filter(
(target) => (target) =>
@@ -944,48 +884,6 @@ function ProxyResourceTargetsForm({
</SettingsSectionDescription> </SettingsSectionDescription>
</SettingsSectionHeader> </SettingsSectionHeader>
<SettingsSectionBody> <SettingsSectionBody>
<div className="flex items-center gap-3 mb-4">
<span className="text-sm font-medium">Target Type</span>
<Select
value={targetMode}
onValueChange={async (v) => {
const mode = v as
| "http"
| "ssh"
| "rdp"
| "vnc";
setTargetMode(mode);
try {
await api.post(
`/resource/${resource.resourceId}`,
{ browserAccessType: mode }
);
updateResource({ browserAccessType: mode });
} catch (err) {
toast({
variant: "destructive",
title: t("settingsErrorUpdate"),
description: formatAxiosError(
err,
t("settingsErrorUpdateDescription")
)
});
}
}}
>
<SelectTrigger className="w-36">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="http">HTTP</SelectItem>
<SelectItem value="ssh">SSH</SelectItem>
<SelectItem value="rdp">RDP</SelectItem>
<SelectItem value="vnc">VNC</SelectItem>
</SelectContent>
</Select>
</div>
{targetMode === "http" ? (
<>
{targets.length > 0 ? ( {targets.length > 0 ? (
<> <>
<div className="overflow-x-auto"> <div className="overflow-x-auto">
@@ -994,14 +892,11 @@ function ProxyResourceTargetsForm({
{table {table
.getHeaderGroups() .getHeaderGroups()
.map((headerGroup) => ( .map((headerGroup) => (
<TableRow <TableRow key={headerGroup.id}>
key={headerGroup.id}
>
{headerGroup.headers.map( {headerGroup.headers.map(
(header) => { (header) => {
const isActionsColumn = const isActionsColumn =
header header.column
.column
.id === .id ===
"actions"; "actions";
return ( return (
@@ -1032,23 +927,16 @@ function ProxyResourceTargetsForm({
))} ))}
</TableHeader> </TableHeader>
<TableBody> <TableBody>
{table.getRowModel().rows {table.getRowModel().rows?.length ? (
?.length ? (
table table
.getRowModel() .getRowModel()
.rows.map((row) => ( .rows.map((row) => (
<TableRow <TableRow key={row.id}>
key={row.id}
>
{row {row
.getVisibleCells() .getVisibleCells()
.map( .map((cell) => {
(
cell
) => {
const isActionsColumn = const isActionsColumn =
cell cell.column
.column
.id === .id ===
"actions"; "actions";
return ( return (
@@ -1071,16 +959,13 @@ function ProxyResourceTargetsForm({
)} )}
</TableCell> </TableCell>
); );
} })}
)}
</TableRow> </TableRow>
)) ))
) : ( ) : (
<TableRow> <TableRow>
<TableCell <TableCell
colSpan={ colSpan={columns.length}
columns.length
}
className="h-24 text-center" className="h-24 text-center"
> >
{t("targetNoOne")} {t("targetNoOne")}
@@ -1106,9 +991,7 @@ function ProxyResourceTargetsForm({
<Switch <Switch
id="advanced-mode-toggle" id="advanced-mode-toggle"
checked={isAdvancedMode} checked={isAdvancedMode}
onCheckedChange={ onCheckedChange={setIsAdvancedMode}
setIsAdvancedMode
}
/> />
<label <label
htmlFor="advanced-mode-toggle" htmlFor="advanced-mode-toggle"
@@ -1125,10 +1008,7 @@ function ProxyResourceTargetsForm({
<p className="text-muted-foreground mb-4"> <p className="text-muted-foreground mb-4">
{t("targetNoOne")} {t("targetNoOne")}
</p> </p>
<Button <Button onClick={addNewTarget} variant="outline">
onClick={addNewTarget}
variant="outline"
>
<Plus className="h-4 w-4 mr-2" /> <Plus className="h-4 w-4 mr-2" />
{t("addTarget")} {t("addTarget")}
</Button> </Button>
@@ -1136,8 +1016,7 @@ function ProxyResourceTargetsForm({
)} )}
{build === "saas" && {build === "saas" &&
targets.length > 1 && targets.length > 1 &&
new Set(targets.map((t) => t.siteId)).size > new Set(targets.map((t) => t.siteId)).size > 1 && (
1 && (
<p className="text-sm text-muted-foreground mt-3"> <p className="text-sm text-muted-foreground mt-3">
{t("proxyMultiSiteRoundRobinNodeHelp")}{" "} {t("proxyMultiSiteRoundRobinNodeHelp")}{" "}
<a <a
@@ -1152,71 +1031,6 @@ function ProxyResourceTargetsForm({
. .
</p> </p>
)} )}
</>
) : (
<div className="space-y-4">
<div className="grid grid-cols-2 gap-4">
<div className="space-y-2">
<label className="text-sm font-medium">
Destination
</label>
<Input
placeholder="192.168.1.1"
value={bgDestination}
onChange={(e) =>
setBgDestination(e.target.value)
}
/>
</div>
<div className="space-y-2">
<label className="text-sm font-medium">
Port
</label>
<Input
type="number"
placeholder={
targetMode === "rdp"
? "3389"
: targetMode === "ssh"
? "22"
: "5900"
}
value={bgDestinationPort}
onChange={(e) =>
setBgDestinationPort(e.target.value)
}
/>
</div>
</div>
{sites.length > 1 && (
<div className="space-y-2">
<label className="text-sm font-medium">
Site
</label>
<Select
value={bgSiteId ? String(bgSiteId) : ""}
onValueChange={(v) =>
setBgSiteId(Number(v))
}
>
<SelectTrigger>
<SelectValue placeholder="Select a site" />
</SelectTrigger>
<SelectContent>
{sites.map((site) => (
<SelectItem
key={site.siteId}
value={String(site.siteId)}
>
{site.name}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
)}
</div>
)}
</SettingsSectionBody> </SettingsSectionBody>
<form className="self-end mt-4" action={formAction}> <form className="self-end mt-4" action={formAction}>

View File

@@ -250,7 +250,7 @@ export default function Page() {
// SSH-specific state // SSH-specific state
const [sshServerMode, setSshServerMode] = useState<"standard" | "native">( const [sshServerMode, setSshServerMode] = useState<"standard" | "native">(
"standard" "native"
); );
const [pamMode, setPamMode] = useState<"passthrough" | "push">( const [pamMode, setPamMode] = useState<"passthrough" | "push">(
"passthrough" "passthrough"
@@ -544,7 +544,8 @@ export default function Page() {
try { try {
const payload: any = { const payload: any = {
name: baseData.name, name: baseData.name,
http: isHttpResource http: isHttpResource,
browserAccessType: resourceType
}; };
let sanitizedSubdomain: string | undefined; let sanitizedSubdomain: string | undefined;
@@ -1265,7 +1266,7 @@ export default function Page() {
{t("resourceCreateGeneral")} {t("resourceCreateGeneral")}
</SettingsSectionTitle> </SettingsSectionTitle>
<SettingsSectionDescription> <SettingsSectionDescription>
{t("resourceCreateDescription")} {t("resourceCreateGeneralDescription")}
</SettingsSectionDescription> </SettingsSectionDescription>
</SettingsSectionHeader> </SettingsSectionHeader>
<SettingsSectionBody> <SettingsSectionBody>