mirror of
https://github.com/fosrl/pangolin.git
synced 2026-09-13 14:31:01 +02:00
make form grids more consistent
This commit is contained in:
@@ -10,6 +10,8 @@ import { formatAxiosError } from "@app/lib/api";
|
|||||||
import { AxiosResponse } from "axios";
|
import { AxiosResponse } from "axios";
|
||||||
import {
|
import {
|
||||||
SettingsContainer,
|
SettingsContainer,
|
||||||
|
SettingsFormCell,
|
||||||
|
SettingsFormGrid,
|
||||||
SettingsSection,
|
SettingsSection,
|
||||||
SettingsSectionHeader,
|
SettingsSectionHeader,
|
||||||
SettingsSectionTitle,
|
SettingsSectionTitle,
|
||||||
@@ -1324,7 +1326,8 @@ export default function BillingPage() {
|
|||||||
</SettingsSectionDescription>
|
</SettingsSectionDescription>
|
||||||
</SettingsSectionHeader>
|
</SettingsSectionHeader>
|
||||||
<SettingsSectionBody>
|
<SettingsSectionBody>
|
||||||
<div className="w-full md:w-1/2">
|
<SettingsFormGrid>
|
||||||
|
<SettingsFormCell span="half">
|
||||||
<div className="flex flex-col md:flex-row items-start md:items-center justify-between gap-4 border rounded-lg p-4">
|
<div className="flex flex-col md:flex-row items-start md:items-center justify-between gap-4 border rounded-lg p-4">
|
||||||
<div>
|
<div>
|
||||||
<div className="text-sm text-muted-foreground mb-1">
|
<div className="text-sm text-muted-foreground mb-1">
|
||||||
@@ -1359,7 +1362,8 @@ export default function BillingPage() {
|
|||||||
"Manage your subscription for paid self-hosted license keys and download invoices."}
|
"Manage your subscription for paid self-hosted license keys and download invoices."}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</SettingsFormCell>
|
||||||
|
</SettingsFormGrid>
|
||||||
</SettingsSectionBody>
|
</SettingsSectionBody>
|
||||||
</SettingsSection>
|
</SettingsSection>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ import {
|
|||||||
} from "@app/components/InfoSection";
|
} from "@app/components/InfoSection";
|
||||||
import {
|
import {
|
||||||
SettingsContainer,
|
SettingsContainer,
|
||||||
|
SettingsFormCell,
|
||||||
|
SettingsFormGrid,
|
||||||
SettingsSection,
|
SettingsSection,
|
||||||
SettingsSectionBody,
|
SettingsSectionBody,
|
||||||
SettingsSectionDescription,
|
SettingsSectionDescription,
|
||||||
@@ -257,9 +259,10 @@ export default function Page() {
|
|||||||
e.preventDefault(); // block default enter refresh
|
e.preventDefault(); // block default enter refresh
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
className="space-y-4 grid gap-4 grid-cols-1 md:grid-cols-2 items-start"
|
|
||||||
id="create-client-form"
|
id="create-client-form"
|
||||||
>
|
>
|
||||||
|
<SettingsFormGrid>
|
||||||
|
<SettingsFormCell span="half">
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="name"
|
name="name"
|
||||||
@@ -283,7 +286,8 @@ export default function Page() {
|
|||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
<div className="flex items-center justify-end md:col-start-2">
|
</SettingsFormCell>
|
||||||
|
<SettingsFormCell className="flex items-center justify-end md:col-span-2">
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
@@ -302,15 +306,18 @@ export default function Page() {
|
|||||||
)}
|
)}
|
||||||
{t("advancedSettings")}
|
{t("advancedSettings")}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</SettingsFormCell>
|
||||||
{showAdvancedSettings && (
|
{showAdvancedSettings && (
|
||||||
|
<SettingsFormCell span="full">
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="subnet"
|
name="subnet"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem className="md:col-start-1 md:col-span-2">
|
<FormItem>
|
||||||
<FormLabel>
|
<FormLabel>
|
||||||
{t("clientAddress")}
|
{t(
|
||||||
|
"clientAddress"
|
||||||
|
)}
|
||||||
</FormLabel>
|
</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input
|
<Input
|
||||||
@@ -330,7 +337,9 @@ export default function Page() {
|
|||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
</SettingsFormCell>
|
||||||
)}
|
)}
|
||||||
|
</SettingsFormGrid>
|
||||||
</form>
|
</form>
|
||||||
</Form>
|
</Form>
|
||||||
</SettingsSectionBody>
|
</SettingsSectionBody>
|
||||||
|
|||||||
@@ -20,6 +20,8 @@ import {
|
|||||||
SettingsSectionBody,
|
SettingsSectionBody,
|
||||||
SettingsSectionDescription,
|
SettingsSectionDescription,
|
||||||
SettingsSectionFooter,
|
SettingsSectionFooter,
|
||||||
|
SettingsFormCell,
|
||||||
|
SettingsFormGrid,
|
||||||
SettingsSectionForm,
|
SettingsSectionForm,
|
||||||
SettingsSectionHeader,
|
SettingsSectionHeader,
|
||||||
SettingsSectionTitle
|
SettingsSectionTitle
|
||||||
@@ -616,9 +618,10 @@ export default function GeneralForm() {
|
|||||||
<Form {...form}>
|
<Form {...form}>
|
||||||
<form
|
<form
|
||||||
action={formAction}
|
action={formAction}
|
||||||
className="space-y-4"
|
|
||||||
id="general-settings-form"
|
id="general-settings-form"
|
||||||
>
|
>
|
||||||
|
<SettingsFormGrid>
|
||||||
|
<SettingsFormCell span="full">
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="enabled"
|
name="enabled"
|
||||||
@@ -652,8 +655,9 @@ export default function GeneralForm() {
|
|||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
</SettingsFormCell>
|
||||||
|
|
||||||
<div className="grid grid-cols-2 gap-4">
|
<SettingsFormCell span="half">
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="name"
|
name="name"
|
||||||
@@ -663,13 +667,17 @@ export default function GeneralForm() {
|
|||||||
{t("name")}
|
{t("name")}
|
||||||
</FormLabel>
|
</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input {...field} />
|
<Input
|
||||||
|
{...field}
|
||||||
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
</SettingsFormCell>
|
||||||
|
|
||||||
|
<SettingsFormCell span="half">
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="niceId"
|
name="niceId"
|
||||||
@@ -690,12 +698,12 @@ export default function GeneralForm() {
|
|||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</SettingsFormCell>
|
||||||
|
|
||||||
{!["http", "ssh", "rdp", "vnc"].includes(
|
{!["http", "ssh", "rdp", "vnc"].includes(
|
||||||
resource.mode
|
resource.mode
|
||||||
) && (
|
) && (
|
||||||
<>
|
<SettingsFormCell span="half">
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="proxyPort"
|
name="proxyPort"
|
||||||
@@ -717,9 +725,12 @@ export default function GeneralForm() {
|
|||||||
)
|
)
|
||||||
: ""
|
: ""
|
||||||
}
|
}
|
||||||
onChange={(e) =>
|
onChange={(
|
||||||
|
e
|
||||||
|
) =>
|
||||||
field.onChange(
|
field.onChange(
|
||||||
e.target
|
e
|
||||||
|
.target
|
||||||
.value
|
.value
|
||||||
? parseInt(
|
? parseInt(
|
||||||
e
|
e
|
||||||
@@ -740,17 +751,19 @@ export default function GeneralForm() {
|
|||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</>
|
</SettingsFormCell>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{["http", "ssh", "rdp", "vnc"].includes(
|
{["http", "ssh", "rdp", "vnc"].includes(
|
||||||
resource.mode
|
resource.mode
|
||||||
) && (
|
) && (
|
||||||
<div className="space-y-4">
|
<SettingsFormCell span="full">
|
||||||
<div id="resource-domain-picker">
|
<div id="resource-domain-picker">
|
||||||
<DomainPicker
|
<DomainPicker
|
||||||
allowWildcard={true}
|
allowWildcard={true}
|
||||||
key={resource.resourceId}
|
key={
|
||||||
|
resource.resourceId
|
||||||
|
}
|
||||||
orgId={orgId as string}
|
orgId={orgId as string}
|
||||||
cols={2}
|
cols={2}
|
||||||
defaultSubdomain={
|
defaultSubdomain={
|
||||||
@@ -767,7 +780,9 @@ export default function GeneralForm() {
|
|||||||
resourceFullDomainName ||
|
resourceFullDomainName ||
|
||||||
undefined
|
undefined
|
||||||
}
|
}
|
||||||
onDomainChange={(res) => {
|
onDomainChange={(
|
||||||
|
res
|
||||||
|
) => {
|
||||||
if (res === null) {
|
if (res === null) {
|
||||||
form.setValue(
|
form.setValue(
|
||||||
"domainId",
|
"domainId",
|
||||||
@@ -797,9 +812,10 @@ export default function GeneralForm() {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</SettingsFormCell>
|
||||||
)}
|
)}
|
||||||
{showResourcePolicy && (
|
{showResourcePolicy && (
|
||||||
|
<SettingsFormCell span="half">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<FormLabel>
|
<FormLabel>
|
||||||
{t("sharedPolicy")}
|
{t("sharedPolicy")}
|
||||||
@@ -810,13 +826,17 @@ export default function GeneralForm() {
|
|||||||
"none"
|
"none"
|
||||||
}
|
}
|
||||||
orgId={org.org.orgId}
|
orgId={org.org.orgId}
|
||||||
value={selectedSharedPolicyId}
|
value={
|
||||||
|
selectedSharedPolicyId
|
||||||
|
}
|
||||||
onChange={
|
onChange={
|
||||||
setSelectedSharedPolicyId
|
setSelectedSharedPolicyId
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
</SettingsFormCell>
|
||||||
)}
|
)}
|
||||||
|
</SettingsFormGrid>
|
||||||
</form>
|
</form>
|
||||||
</Form>
|
</Form>
|
||||||
</SettingsSectionForm>
|
</SettingsSectionForm>
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import {
|
|||||||
SettingsSection,
|
SettingsSection,
|
||||||
SettingsSectionBody,
|
SettingsSectionBody,
|
||||||
SettingsSectionDescription,
|
SettingsSectionDescription,
|
||||||
|
SettingsFormCell,
|
||||||
|
SettingsFormGrid,
|
||||||
SettingsSectionForm,
|
SettingsSectionForm,
|
||||||
SettingsSectionHeader,
|
SettingsSectionHeader,
|
||||||
SettingsSectionTitle,
|
SettingsSectionTitle,
|
||||||
@@ -410,17 +412,25 @@ function SshServerForm({
|
|||||||
<Form {...form}>
|
<Form {...form}>
|
||||||
<SettingsSectionBody>
|
<SettingsSectionBody>
|
||||||
<SettingsSectionForm variant="half">
|
<SettingsSectionForm variant="half">
|
||||||
|
<SettingsFormGrid>
|
||||||
|
<SettingsFormCell span="full">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<p className="font-semibold text-sm">{t("sshServerMode")}</p>
|
<p className="font-semibold text-sm">
|
||||||
|
{t("sshServerMode")}
|
||||||
|
</p>
|
||||||
<Badge variant="secondary">
|
<Badge variant="secondary">
|
||||||
{sshServerMode == "standard"
|
{sshServerMode == "standard"
|
||||||
? t("sshServerModeStandard")
|
? t("sshServerModeStandard")
|
||||||
: t("sshServerModePangolin")}
|
: t("sshServerModePangolin")}
|
||||||
</Badge>
|
</Badge>
|
||||||
</div>
|
</div>
|
||||||
|
</SettingsFormCell>
|
||||||
|
|
||||||
|
<SettingsFormCell span="full">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<p className="font-semibold text-sm">{t("sshAuthenticationMethod")}</p>
|
<p className="font-semibold text-sm">
|
||||||
|
{t("sshAuthenticationMethod")}
|
||||||
|
</p>
|
||||||
<StrategySelect<"passthrough" | "push">
|
<StrategySelect<"passthrough" | "push">
|
||||||
value={pamMode}
|
value={pamMode}
|
||||||
options={authMethodOptions}
|
options={authMethodOptions}
|
||||||
@@ -432,10 +442,14 @@ function SshServerForm({
|
|||||||
cols={2}
|
cols={2}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
</SettingsFormCell>
|
||||||
|
|
||||||
{showDaemonLocation && (
|
{showDaemonLocation && (
|
||||||
|
<SettingsFormCell span="full">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<p className="font-semibold text-sm">{t("sshAuthDaemonLocation")}</p>
|
<p className="font-semibold text-sm">
|
||||||
|
{t("sshAuthDaemonLocation")}
|
||||||
|
</p>
|
||||||
<StrategySelect<"site" | "remote">
|
<StrategySelect<"site" | "remote">
|
||||||
value={standardDaemonLocation}
|
value={standardDaemonLocation}
|
||||||
options={daemonLocationOptions}
|
options={daemonLocationOptions}
|
||||||
@@ -443,7 +457,9 @@ function SshServerForm({
|
|||||||
form.setValue(
|
form.setValue(
|
||||||
"standardDaemonLocation",
|
"standardDaemonLocation",
|
||||||
value,
|
value,
|
||||||
{ shouldValidate: true }
|
{
|
||||||
|
shouldValidate: true
|
||||||
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
cols={2}
|
cols={2}
|
||||||
@@ -461,10 +477,11 @@ function SshServerForm({
|
|||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
</SettingsFormCell>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{showDaemonPort && (
|
{showDaemonPort && (
|
||||||
<div className="w-full md:w-1/2">
|
<SettingsFormCell span="half">
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="authDaemonPort"
|
name="authDaemonPort"
|
||||||
@@ -485,19 +502,24 @@ function SshServerForm({
|
|||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</SettingsFormCell>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="space-y-3">
|
<SettingsFormCell span="full">
|
||||||
<SettingsSubsectionHeader>
|
<SettingsSubsectionHeader>
|
||||||
<SettingsSubsectionTitle>
|
<SettingsSubsectionTitle>
|
||||||
{t("sshServerDestination")}
|
{t("sshServerDestination")}
|
||||||
</SettingsSubsectionTitle>
|
</SettingsSubsectionTitle>
|
||||||
<SettingsSubsectionDescription>
|
<SettingsSubsectionDescription>
|
||||||
{t("sshServerDestinationDescription")}
|
{t(
|
||||||
|
"sshServerDestinationDescription"
|
||||||
|
)}
|
||||||
</SettingsSubsectionDescription>
|
</SettingsSubsectionDescription>
|
||||||
</SettingsSubsectionHeader>
|
</SettingsSubsectionHeader>
|
||||||
|
</SettingsFormCell>
|
||||||
|
|
||||||
{isNative ? (
|
{isNative ? (
|
||||||
|
<SettingsFormCell span="half">
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="selectedNativeSite"
|
name="selectedNativeSite"
|
||||||
@@ -514,7 +536,7 @@ function SshServerForm({
|
|||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
role="combobox"
|
role="combobox"
|
||||||
className="w-full max-w-xs justify-between font-normal"
|
className="w-full justify-between font-normal"
|
||||||
>
|
>
|
||||||
<span className="truncate">
|
<span className="truncate">
|
||||||
{selectedNativeSite?.name ??
|
{selectedNativeSite?.name ??
|
||||||
@@ -554,7 +576,9 @@ function SshServerForm({
|
|||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
</SettingsFormCell>
|
||||||
) : useMultiSiteTargetForm ? (
|
) : useMultiSiteTargetForm ? (
|
||||||
|
<SettingsFormCell span="full">
|
||||||
<BrowserGatewayTargetForm
|
<BrowserGatewayTargetForm
|
||||||
control={form.control}
|
control={form.control}
|
||||||
orgId={orgId}
|
orgId={orgId}
|
||||||
@@ -565,7 +589,9 @@ function SshServerForm({
|
|||||||
learnMoreHref="https://docs.pangolin.net/manage/resources/public/ssh"
|
learnMoreHref="https://docs.pangolin.net/manage/resources/public/ssh"
|
||||||
defaultPort={22}
|
defaultPort={22}
|
||||||
/>
|
/>
|
||||||
|
</SettingsFormCell>
|
||||||
) : (
|
) : (
|
||||||
|
<SettingsFormCell span="full">
|
||||||
<BrowserGatewayTargetForm
|
<BrowserGatewayTargetForm
|
||||||
control={form.control}
|
control={form.control}
|
||||||
orgId={orgId}
|
orgId={orgId}
|
||||||
@@ -576,8 +602,9 @@ function SshServerForm({
|
|||||||
learnMoreHref="https://docs.pangolin.net/manage/resources/public/ssh"
|
learnMoreHref="https://docs.pangolin.net/manage/resources/public/ssh"
|
||||||
defaultPort={22}
|
defaultPort={22}
|
||||||
/>
|
/>
|
||||||
|
</SettingsFormCell>
|
||||||
)}
|
)}
|
||||||
</div>
|
</SettingsFormGrid>
|
||||||
</SettingsSectionForm>
|
</SettingsSectionForm>
|
||||||
</SettingsSectionBody>
|
</SettingsSectionBody>
|
||||||
<form action={formAction} className="flex justify-end mt-4">
|
<form action={formAction} className="flex justify-end mt-4">
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import CopyTextBox from "@app/components/CopyTextBox";
|
|||||||
import DomainPicker from "@app/components/DomainPicker";
|
import DomainPicker from "@app/components/DomainPicker";
|
||||||
import {
|
import {
|
||||||
SettingsContainer,
|
SettingsContainer,
|
||||||
|
SettingsFormCell,
|
||||||
|
SettingsFormGrid,
|
||||||
SettingsSection,
|
SettingsSection,
|
||||||
SettingsSectionBody,
|
SettingsSectionBody,
|
||||||
SettingsSectionDescription,
|
SettingsSectionDescription,
|
||||||
@@ -806,24 +808,33 @@ export default function Page() {
|
|||||||
</SettingsSectionHeader>
|
</SettingsSectionHeader>
|
||||||
<SettingsSectionBody>
|
<SettingsSectionBody>
|
||||||
<SettingsSectionForm variant="half">
|
<SettingsSectionForm variant="half">
|
||||||
{/* Name */}
|
<SettingsFormGrid>
|
||||||
|
<SettingsFormCell span="half">
|
||||||
<Form {...baseForm}>
|
<Form {...baseForm}>
|
||||||
<form
|
<form
|
||||||
onKeyDown={(e) => {
|
onKeyDown={(e) => {
|
||||||
if (e.key === "Enter") {
|
if (
|
||||||
|
e.key ===
|
||||||
|
"Enter"
|
||||||
|
) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
className="grid gap-4 grid-cols-1 md:grid-cols-2 items-start"
|
|
||||||
id="base-resource-form"
|
id="base-resource-form"
|
||||||
>
|
>
|
||||||
<FormField
|
<FormField
|
||||||
control={baseForm.control}
|
control={
|
||||||
|
baseForm.control
|
||||||
|
}
|
||||||
name="name"
|
name="name"
|
||||||
render={({ field }) => (
|
render={({
|
||||||
|
field
|
||||||
|
}) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>
|
<FormLabel>
|
||||||
{t("name")}
|
{t(
|
||||||
|
"name"
|
||||||
|
)}
|
||||||
</FormLabel>
|
</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input
|
<Input
|
||||||
@@ -841,8 +852,9 @@ export default function Page() {
|
|||||||
/>
|
/>
|
||||||
</form>
|
</form>
|
||||||
</Form>
|
</Form>
|
||||||
|
</SettingsFormCell>
|
||||||
|
|
||||||
{/* Inline Type Selector */}
|
<SettingsFormCell span="full">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<p className="text-sm font-medium">
|
<p className="text-sm font-medium">
|
||||||
{t("type")}
|
{t("type")}
|
||||||
@@ -850,19 +862,26 @@ export default function Page() {
|
|||||||
<OptionSelect<NewResourceType>
|
<OptionSelect<NewResourceType>
|
||||||
options={typeOptions}
|
options={typeOptions}
|
||||||
value={resourceType}
|
value={resourceType}
|
||||||
onChange={setResourceType}
|
onChange={
|
||||||
|
setResourceType
|
||||||
|
}
|
||||||
cols={6}
|
cols={6}
|
||||||
/>
|
/>
|
||||||
<p className="text-sm text-muted-foreground">
|
<p className="text-sm text-muted-foreground">
|
||||||
{t("resourceTypeDescription")}
|
{t(
|
||||||
|
"resourceTypeDescription"
|
||||||
|
)}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
</SettingsFormCell>
|
||||||
|
|
||||||
{/* Domain/Subdomain (HTTP-based types) */}
|
|
||||||
{isHttpResource && (
|
{isHttpResource && (
|
||||||
|
<SettingsFormCell span="full">
|
||||||
<Form {...httpForm}>
|
<Form {...httpForm}>
|
||||||
<FormField
|
<FormField
|
||||||
control={httpForm.control}
|
control={
|
||||||
|
httpForm.control
|
||||||
|
}
|
||||||
name="domainId"
|
name="domainId"
|
||||||
render={() => (
|
render={() => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
@@ -880,7 +899,9 @@ export default function Page() {
|
|||||||
onDomainChange={(
|
onDomainChange={(
|
||||||
res
|
res
|
||||||
) => {
|
) => {
|
||||||
if (!res)
|
if (
|
||||||
|
!res
|
||||||
|
)
|
||||||
return;
|
return;
|
||||||
httpForm.setValue(
|
httpForm.setValue(
|
||||||
"subdomain",
|
"subdomain",
|
||||||
@@ -910,18 +931,21 @@ export default function Page() {
|
|||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</Form>
|
</Form>
|
||||||
|
</SettingsFormCell>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Proxy Port (TCP/UDP types) */}
|
|
||||||
{!isHttpResource && (
|
{!isHttpResource && (
|
||||||
|
<SettingsFormCell span="half">
|
||||||
<Form {...tcpUdpForm}>
|
<Form {...tcpUdpForm}>
|
||||||
<form
|
<form
|
||||||
onKeyDown={(e) => {
|
onKeyDown={(e) => {
|
||||||
if (e.key === "Enter") {
|
if (
|
||||||
|
e.key ===
|
||||||
|
"Enter"
|
||||||
|
) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
className="grid gap-4 grid-cols-1 md:grid-cols-2 items-start"
|
|
||||||
id="tcp-udp-settings-form"
|
id="tcp-udp-settings-form"
|
||||||
>
|
>
|
||||||
<FormField
|
<FormField
|
||||||
@@ -929,7 +953,9 @@ export default function Page() {
|
|||||||
tcpUdpForm.control
|
tcpUdpForm.control
|
||||||
}
|
}
|
||||||
name="proxyPort"
|
name="proxyPort"
|
||||||
render={({ field }) => (
|
render={({
|
||||||
|
field
|
||||||
|
}) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>
|
<FormLabel>
|
||||||
{t(
|
{t(
|
||||||
@@ -971,7 +997,9 @@ export default function Page() {
|
|||||||
/>
|
/>
|
||||||
</form>
|
</form>
|
||||||
</Form>
|
</Form>
|
||||||
|
</SettingsFormCell>
|
||||||
)}
|
)}
|
||||||
|
</SettingsFormGrid>
|
||||||
</SettingsSectionForm>
|
</SettingsSectionForm>
|
||||||
</SettingsSectionBody>
|
</SettingsSectionBody>
|
||||||
</SettingsSection>
|
</SettingsSection>
|
||||||
@@ -1005,21 +1033,34 @@ export default function Page() {
|
|||||||
>
|
>
|
||||||
<SettingsSectionBody>
|
<SettingsSectionBody>
|
||||||
<SettingsSectionForm variant="half">
|
<SettingsSectionForm variant="half">
|
||||||
{/* Mode */}
|
<SettingsFormGrid>
|
||||||
|
<SettingsFormCell span="full">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<p className="font-semibold text-sm">{t("sshServerMode")}</p>
|
<p className="font-semibold text-sm">
|
||||||
|
{t("sshServerMode")}
|
||||||
|
</p>
|
||||||
<StrategySelect<
|
<StrategySelect<
|
||||||
"standard" | "native"
|
"standard" | "native"
|
||||||
>
|
>
|
||||||
value={sshServerMode}
|
value={sshServerMode}
|
||||||
options={sshModeOptions}
|
options={
|
||||||
onChange={setSshServerMode}
|
sshModeOptions
|
||||||
|
}
|
||||||
|
onChange={
|
||||||
|
setSshServerMode
|
||||||
|
}
|
||||||
cols={2}
|
cols={2}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
</SettingsFormCell>
|
||||||
|
|
||||||
|
<SettingsFormCell span="full">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<p className="font-semibold text-sm">{t("sshAuthenticationMethod")}</p>
|
<p className="font-semibold text-sm">
|
||||||
|
{t(
|
||||||
|
"sshAuthenticationMethod"
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
<StrategySelect<
|
<StrategySelect<
|
||||||
"passthrough" | "push"
|
"passthrough" | "push"
|
||||||
>
|
>
|
||||||
@@ -1031,11 +1072,16 @@ export default function Page() {
|
|||||||
cols={2}
|
cols={2}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
</SettingsFormCell>
|
||||||
|
|
||||||
{/* Daemon Location (standard + push) */}
|
|
||||||
{showDaemonLocation && (
|
{showDaemonLocation && (
|
||||||
|
<SettingsFormCell span="full">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<p className="font-semibold text-sm">{t("sshAuthDaemonLocation")}</p>
|
<p className="font-semibold text-sm">
|
||||||
|
{t(
|
||||||
|
"sshAuthDaemonLocation"
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
<StrategySelect<
|
<StrategySelect<
|
||||||
"site" | "remote"
|
"site" | "remote"
|
||||||
>
|
>
|
||||||
@@ -1060,17 +1106,21 @@ export default function Page() {
|
|||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
className="text-primary hover:underline inline-flex items-center gap-1"
|
className="text-primary hover:underline inline-flex items-center gap-1"
|
||||||
>
|
>
|
||||||
{t("learnMore")}
|
{t(
|
||||||
|
"learnMore"
|
||||||
|
)}
|
||||||
<ExternalLink className="size-3.5 shrink-0" />
|
<ExternalLink className="size-3.5 shrink-0" />
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
</SettingsFormCell>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Daemon Port (standard + push + remote) */}
|
|
||||||
{showDaemonPort && (
|
{showDaemonPort && (
|
||||||
<Form {...sshDaemonPortForm}>
|
<SettingsFormCell span="half">
|
||||||
<div className="w-full md:w-1/2">
|
<Form
|
||||||
|
{...sshDaemonPortForm}
|
||||||
|
>
|
||||||
<FormField
|
<FormField
|
||||||
control={
|
control={
|
||||||
sshDaemonPortForm.control
|
sshDaemonPortForm.control
|
||||||
@@ -1101,12 +1151,11 @@ export default function Page() {
|
|||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
</Form>
|
</Form>
|
||||||
|
</SettingsFormCell>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Server Destination */}
|
<SettingsFormCell span="full">
|
||||||
<div className="space-y-3">
|
|
||||||
<SettingsSubsectionHeader>
|
<SettingsSubsectionHeader>
|
||||||
<SettingsSubsectionTitle>
|
<SettingsSubsectionTitle>
|
||||||
{t(
|
{t(
|
||||||
@@ -1119,18 +1168,25 @@ export default function Page() {
|
|||||||
)}
|
)}
|
||||||
</SettingsSubsectionDescription>
|
</SettingsSubsectionDescription>
|
||||||
</SettingsSubsectionHeader>
|
</SettingsSubsectionHeader>
|
||||||
|
</SettingsFormCell>
|
||||||
|
|
||||||
{isNative ? (
|
{isNative ? (
|
||||||
|
<SettingsFormCell span="half">
|
||||||
<Popover
|
<Popover
|
||||||
open={nativeSiteOpen}
|
open={
|
||||||
|
nativeSiteOpen
|
||||||
|
}
|
||||||
onOpenChange={
|
onOpenChange={
|
||||||
setNativeSiteOpen
|
setNativeSiteOpen
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<PopoverTrigger asChild>
|
<PopoverTrigger
|
||||||
|
asChild
|
||||||
|
>
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
role="combobox"
|
role="combobox"
|
||||||
className="w-full md:w-1/2 justify-between font-normal"
|
className="w-full justify-between font-normal"
|
||||||
>
|
>
|
||||||
<span className="truncate">
|
<span className="truncate">
|
||||||
{nativeSelectedSite?.name ??
|
{nativeSelectedSite?.name ??
|
||||||
@@ -1162,10 +1218,12 @@ export default function Page() {
|
|||||||
/>
|
/>
|
||||||
</PopoverContent>
|
</PopoverContent>
|
||||||
</Popover>
|
</Popover>
|
||||||
|
</SettingsFormCell>
|
||||||
) : standardDaemonLocation !==
|
) : standardDaemonLocation !==
|
||||||
"site" ||
|
"site" ||
|
||||||
pamMode ===
|
pamMode ===
|
||||||
"passthrough" ? (
|
"passthrough" ? (
|
||||||
|
<SettingsFormCell span="full">
|
||||||
<Form {...bgTargetForm}>
|
<Form {...bgTargetForm}>
|
||||||
<BrowserGatewayTargetForm
|
<BrowserGatewayTargetForm
|
||||||
control={
|
control={
|
||||||
@@ -1182,7 +1240,9 @@ export default function Page() {
|
|||||||
defaultPort={22}
|
defaultPort={22}
|
||||||
/>
|
/>
|
||||||
</Form>
|
</Form>
|
||||||
|
</SettingsFormCell>
|
||||||
) : (
|
) : (
|
||||||
|
<SettingsFormCell span="full">
|
||||||
<Form {...bgTargetForm}>
|
<Form {...bgTargetForm}>
|
||||||
<BrowserGatewayTargetForm
|
<BrowserGatewayTargetForm
|
||||||
control={
|
control={
|
||||||
@@ -1191,7 +1251,9 @@ export default function Page() {
|
|||||||
orgId={
|
orgId={
|
||||||
orgId as string
|
orgId as string
|
||||||
}
|
}
|
||||||
multiSite={false}
|
multiSite={
|
||||||
|
false
|
||||||
|
}
|
||||||
siteField="selectedSite"
|
siteField="selectedSite"
|
||||||
destinationField="destination"
|
destinationField="destination"
|
||||||
destinationPortField="destinationPort"
|
destinationPortField="destinationPort"
|
||||||
@@ -1199,8 +1261,9 @@ export default function Page() {
|
|||||||
defaultPort={22}
|
defaultPort={22}
|
||||||
/>
|
/>
|
||||||
</Form>
|
</Form>
|
||||||
|
</SettingsFormCell>
|
||||||
)}
|
)}
|
||||||
</div>
|
</SettingsFormGrid>
|
||||||
</SettingsSectionForm>
|
</SettingsSectionForm>
|
||||||
</SettingsSectionBody>
|
</SettingsSectionBody>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
SettingsContainer,
|
SettingsContainer,
|
||||||
|
SettingsFormCell,
|
||||||
|
SettingsFormGrid,
|
||||||
SettingsSection,
|
SettingsSection,
|
||||||
SettingsSectionBody,
|
SettingsSectionBody,
|
||||||
SettingsSectionDescription,
|
SettingsSectionDescription,
|
||||||
@@ -514,9 +516,10 @@ export default function Page() {
|
|||||||
e.preventDefault(); // block default enter refresh
|
e.preventDefault(); // block default enter refresh
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
className="space-y-4 grid gap-4 grid-cols-1 md:grid-cols-2 items-start"
|
|
||||||
id="create-site-form"
|
id="create-site-form"
|
||||||
>
|
>
|
||||||
|
<SettingsFormGrid>
|
||||||
|
<SettingsFormCell span="half">
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="name"
|
name="name"
|
||||||
@@ -540,8 +543,9 @@ export default function Page() {
|
|||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
{form.watch("method") === "newt" && (
|
{form.watch("method") ===
|
||||||
<div className="flex items-center justify-end md:col-start-2">
|
"newt" && (
|
||||||
|
<>
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
@@ -551,24 +555,27 @@ export default function Page() {
|
|||||||
!showAdvancedSettings
|
!showAdvancedSettings
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
className="flex items-center gap-2"
|
className="mt-2 flex items-center gap-2 -ml-3"
|
||||||
>
|
>
|
||||||
{showAdvancedSettings ? (
|
{showAdvancedSettings ? (
|
||||||
<ChevronUp className="h-4 w-4" />
|
<ChevronUp className="h-4 w-4" />
|
||||||
) : (
|
) : (
|
||||||
<ChevronDown className="h-4 w-4" />
|
<ChevronDown className="h-4 w-4" />
|
||||||
)}
|
)}
|
||||||
{t("advancedSettings")}
|
{t(
|
||||||
</Button>
|
"advancedSettings"
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
{form.watch("method") === "newt" &&
|
</Button>
|
||||||
showAdvancedSettings && (
|
{showAdvancedSettings && (
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={
|
||||||
|
form.control
|
||||||
|
}
|
||||||
name="clientAddress"
|
name="clientAddress"
|
||||||
render={({ field }) => (
|
render={({
|
||||||
<FormItem className="md:col-start-1 md:col-span-2">
|
field
|
||||||
|
}) => (
|
||||||
|
<FormItem className="mt-4">
|
||||||
<FormLabel>
|
<FormLabel>
|
||||||
{t(
|
{t(
|
||||||
"siteAddress"
|
"siteAddress"
|
||||||
@@ -606,6 +613,10 @@ export default function Page() {
|
|||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</SettingsFormCell>
|
||||||
|
</SettingsFormGrid>
|
||||||
</form>
|
</form>
|
||||||
</Form>
|
</Form>
|
||||||
</SettingsSectionBody>
|
</SettingsSectionBody>
|
||||||
|
|||||||
@@ -43,6 +43,49 @@ export function SettingsSectionForm({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function SettingsFormGrid({
|
||||||
|
children,
|
||||||
|
className
|
||||||
|
}: {
|
||||||
|
children: React.ReactNode;
|
||||||
|
className?: string;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
"grid grid-cols-1 md:grid-cols-4 gap-4 items-start",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function SettingsFormCell({
|
||||||
|
children,
|
||||||
|
span = "half",
|
||||||
|
className
|
||||||
|
}: {
|
||||||
|
children: React.ReactNode;
|
||||||
|
span?: "quarter" | "half" | "full";
|
||||||
|
className?: string;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
"min-w-0",
|
||||||
|
span === "quarter" && "md:col-span-1",
|
||||||
|
span === "half" && "md:col-span-2",
|
||||||
|
span === "full" && "md:col-span-4",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export function SettingsSectionTitle({
|
export function SettingsSectionTitle({
|
||||||
children
|
children
|
||||||
}: {
|
}: {
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
SettingsFormCell,
|
||||||
|
SettingsFormGrid,
|
||||||
SettingsSection,
|
SettingsSection,
|
||||||
SettingsSectionBody,
|
SettingsSectionBody,
|
||||||
SettingsSectionDescription,
|
SettingsSectionDescription,
|
||||||
@@ -111,7 +113,8 @@ export function PolicyAuthStackSectionCreate({
|
|||||||
</SettingsSectionDescription>
|
</SettingsSectionDescription>
|
||||||
</SettingsSectionHeader>
|
</SettingsSectionHeader>
|
||||||
<SettingsSectionBody>
|
<SettingsSectionBody>
|
||||||
<div className="w-full md:w-1/2">
|
<SettingsFormGrid>
|
||||||
|
<SettingsFormCell span="half">
|
||||||
<PolicyAuthSsoSection
|
<PolicyAuthSsoSection
|
||||||
sso={Boolean(sso)}
|
sso={Boolean(sso)}
|
||||||
onSsoChange={(active) =>
|
onSsoChange={(active) =>
|
||||||
@@ -169,7 +172,8 @@ export function PolicyAuthStackSectionCreate({
|
|||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</div>
|
</SettingsFormCell>
|
||||||
|
</SettingsFormGrid>
|
||||||
|
|
||||||
<SettingsSubsectionHeader>
|
<SettingsSubsectionHeader>
|
||||||
<SettingsSubsectionTitle>
|
<SettingsSubsectionTitle>
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
SettingsFormCell,
|
||||||
|
SettingsFormGrid,
|
||||||
SettingsSection,
|
SettingsSection,
|
||||||
SettingsSectionBody,
|
SettingsSectionBody,
|
||||||
SettingsSectionDescription,
|
SettingsSectionDescription,
|
||||||
@@ -475,7 +477,8 @@ export function PolicyAuthStackSectionEdit({
|
|||||||
{isResourceOverlay && (
|
{isResourceOverlay && (
|
||||||
<SharedPolicyResourceNotice section="authentication" />
|
<SharedPolicyResourceNotice section="authentication" />
|
||||||
)}
|
)}
|
||||||
<div className="w-full md:w-1/2">
|
<SettingsFormGrid>
|
||||||
|
<SettingsFormCell span="half">
|
||||||
<PolicyAuthSsoSection
|
<PolicyAuthSsoSection
|
||||||
sso={Boolean(sso)}
|
sso={Boolean(sso)}
|
||||||
onSsoChange={(active) =>
|
onSsoChange={(active) =>
|
||||||
@@ -508,7 +511,9 @@ export function PolicyAuthStackSectionEdit({
|
|||||||
}
|
}
|
||||||
disabled={isLoading}
|
disabled={isLoading}
|
||||||
restrictAdminRole
|
restrictAdminRole
|
||||||
lockedIds={policyRoleLockedIds}
|
lockedIds={
|
||||||
|
policyRoleLockedIds
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<FormField
|
<FormField
|
||||||
@@ -540,9 +545,13 @@ export function PolicyAuthStackSectionEdit({
|
|||||||
<UsersSelector
|
<UsersSelector
|
||||||
orgId={orgId}
|
orgId={orgId}
|
||||||
selectedUsers={overlayUsers}
|
selectedUsers={overlayUsers}
|
||||||
onSelectUsers={setCombinedUsers}
|
onSelectUsers={
|
||||||
|
setCombinedUsers
|
||||||
|
}
|
||||||
disabled={isLoading}
|
disabled={isLoading}
|
||||||
lockedIds={policyUserLockedIds}
|
lockedIds={
|
||||||
|
policyUserLockedIds
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<FormField
|
<FormField
|
||||||
@@ -569,7 +578,8 @@ export function PolicyAuthStackSectionEdit({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</div>
|
</SettingsFormCell>
|
||||||
|
</SettingsFormGrid>
|
||||||
|
|
||||||
<SettingsSubsectionHeader>
|
<SettingsSubsectionHeader>
|
||||||
<SettingsSubsectionTitle>
|
<SettingsSubsectionTitle>
|
||||||
|
|||||||
@@ -183,7 +183,7 @@ export function SharedPolicySelect({
|
|||||||
role="combobox"
|
role="combobox"
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
className={cn(
|
className={cn(
|
||||||
"w-full justify-between font-normal md:w-1/2",
|
"w-full justify-between font-normal",
|
||||||
value !== null &&
|
value !== null &&
|
||||||
!resolvedLabel &&
|
!resolvedLabel &&
|
||||||
!fetchedPolicy?.name &&
|
!fetchedPolicy?.name &&
|
||||||
|
|||||||
Reference in New Issue
Block a user