mirror of
https://github.com/fosrl/pangolin.git
synced 2025-08-02 00:55:48 +02:00
move proxy related settings to new proxy tab for resource
This commit is contained in:
parent
f4fd33b47f
commit
91b4bb4683
14 changed files with 324 additions and 277 deletions
|
@ -99,6 +99,7 @@ const updateRawResourceBodySchema = z
|
|||
.object({
|
||||
name: z.string().min(1).max(255).optional(),
|
||||
proxyPort: z.number().int().min(1).max(65535).optional(),
|
||||
stickySession: z.boolean().optional(),
|
||||
enabled: z.boolean().optional()
|
||||
})
|
||||
.strict()
|
||||
|
|
|
@ -41,7 +41,7 @@ export async function traefikConfigProvider(
|
|||
orgId: orgs.orgId
|
||||
},
|
||||
enabled: resources.enabled,
|
||||
stickySession: resources.stickySessionk,
|
||||
stickySession: resources.stickySession,
|
||||
tlsServerName: resources.tlsServerName,
|
||||
setHostHeader: resources.setHostHeader
|
||||
})
|
||||
|
@ -288,7 +288,7 @@ export async function traefikConfigProvider(
|
|||
? {
|
||||
sticky: {
|
||||
cookie: {
|
||||
name: "pangolin_sticky",
|
||||
name: "p_sticky", // TODO: make this configurable via config.yml like other cookies
|
||||
secure: resource.ssl,
|
||||
httpOnly: true
|
||||
}
|
||||
|
|
|
@ -234,7 +234,7 @@ export default function GeneralPage() {
|
|||
loading={loadingSave}
|
||||
disabled={loadingSave}
|
||||
>
|
||||
Save Settings
|
||||
Save General Settings
|
||||
</Button>
|
||||
</SettingsSectionFooter>
|
||||
</SettingsSection>
|
||||
|
|
|
@ -57,7 +57,8 @@ export default function SitesTable({ resources, orgId }: ResourcesTableProps) {
|
|||
const api = createApiClient(useEnvContext());
|
||||
|
||||
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
|
||||
const [selectedResource, setSelectedResource] = useState<ResourceRow | null>();
|
||||
const [selectedResource, setSelectedResource] =
|
||||
useState<ResourceRow | null>();
|
||||
|
||||
const deleteResource = (resourceId: number) => {
|
||||
api.delete(`/resource/${resourceId}`)
|
||||
|
@ -238,7 +239,7 @@ export default function SitesTable({ resources, orgId }: ResourcesTableProps) {
|
|||
<span>Not Protected</span>
|
||||
</span>
|
||||
) : (
|
||||
<span>--</span>
|
||||
<span>-</span>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -109,42 +109,8 @@ const TransferFormSchema = z.object({
|
|||
siteId: z.number()
|
||||
});
|
||||
|
||||
const AdvancedFormSchema = z
|
||||
.object({
|
||||
http: z.boolean(),
|
||||
tlsServerName: z.string().optional(),
|
||||
setHostHeader: z.string().optional()
|
||||
})
|
||||
.refine(
|
||||
(data) => {
|
||||
if (data.tlsServerName) {
|
||||
return tlsNameSchema.safeParse(data.tlsServerName).success;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
{
|
||||
message:
|
||||
"Invalid TLS Server Name. Use domain name format, or save empty to remove the TLS Server Name.",
|
||||
path: ["tlsServerName"]
|
||||
}
|
||||
)
|
||||
.refine(
|
||||
(data) => {
|
||||
if (data.setHostHeader) {
|
||||
return tlsNameSchema.safeParse(data.setHostHeader).success;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
{
|
||||
message:
|
||||
"Invalid custom Host Header value. Use domain name format, or save empty to unset the custom Host Header",
|
||||
path: ["tlsServerName"]
|
||||
}
|
||||
);
|
||||
|
||||
type GeneralFormValues = z.infer<typeof GeneralFormSchema>;
|
||||
type TransferFormValues = z.infer<typeof TransferFormSchema>;
|
||||
type AdvancedFormValues = z.infer<typeof AdvancedFormSchema>;
|
||||
|
||||
export default function GeneralForm() {
|
||||
const [formKey, setFormKey] = useState(0);
|
||||
|
@ -185,20 +151,6 @@ export default function GeneralForm() {
|
|||
mode: "onChange"
|
||||
});
|
||||
|
||||
const advancedForm = useForm<AdvancedFormValues>({
|
||||
resolver: zodResolver(AdvancedFormSchema),
|
||||
defaultValues: {
|
||||
http: resource.http,
|
||||
tlsServerName: resource.http
|
||||
? resource.tlsServerName || ""
|
||||
: undefined,
|
||||
setHostHeader: resource.http
|
||||
? resource.setHostHeader || ""
|
||||
: undefined
|
||||
},
|
||||
mode: "onChange"
|
||||
});
|
||||
|
||||
const transferForm = useForm<TransferFormValues>({
|
||||
resolver: zodResolver(TransferFormSchema),
|
||||
defaultValues: {
|
||||
|
@ -327,46 +279,6 @@ export default function GeneralForm() {
|
|||
setTransferLoading(false);
|
||||
}
|
||||
|
||||
async function onSubmitAdvanced(data: AdvancedFormValues) {
|
||||
setSaveLoading(true);
|
||||
|
||||
const res = await api
|
||||
.post<AxiosResponse<UpdateResourceResponse>>(
|
||||
`resource/${resource?.resourceId}`,
|
||||
{
|
||||
tlsServerName: data.http ? data.tlsServerName : undefined,
|
||||
setHostHeader: data.http ? data.setHostHeader : undefined
|
||||
}
|
||||
)
|
||||
.catch((e) => {
|
||||
toast({
|
||||
variant: "destructive",
|
||||
title: "Failed to update resource",
|
||||
description: formatAxiosError(
|
||||
e,
|
||||
"An error occurred while updating the resource"
|
||||
)
|
||||
});
|
||||
});
|
||||
|
||||
if (res && res.status === 200) {
|
||||
toast({
|
||||
title: "Resource updated",
|
||||
description: "The resource has been updated successfully"
|
||||
});
|
||||
|
||||
const resource = res.data.data;
|
||||
|
||||
updateResource({
|
||||
tlsServerName: data.tlsServerName,
|
||||
setHostHeader: data.setHostHeader
|
||||
});
|
||||
|
||||
router.refresh();
|
||||
}
|
||||
setSaveLoading(false);
|
||||
}
|
||||
|
||||
async function toggleResourceEnabled(val: boolean) {
|
||||
const res = await api
|
||||
.post<AxiosResponse<UpdateResourceResponse>>(
|
||||
|
@ -684,82 +596,11 @@ export default function GeneralForm() {
|
|||
disabled={saveLoading}
|
||||
form="general-settings-form"
|
||||
>
|
||||
Save Settings
|
||||
Save General Settings
|
||||
</Button>
|
||||
</SettingsSectionFooter>
|
||||
</SettingsSection>
|
||||
|
||||
{resource.http && (
|
||||
<>
|
||||
<SettingsSection>
|
||||
<SettingsSectionHeader>
|
||||
<SettingsSectionTitle>
|
||||
Advanced
|
||||
</SettingsSectionTitle>
|
||||
<SettingsSectionDescription>
|
||||
Adjust advanced settings for the resource,
|
||||
like customize the Host Header or set a TLS
|
||||
Server Name for SNI based routing.
|
||||
</SettingsSectionDescription>
|
||||
</SettingsSectionHeader>
|
||||
<SettingsSectionBody>
|
||||
<SettingsSectionForm>
|
||||
<Form {...advancedForm}>
|
||||
<form
|
||||
onSubmit={advancedForm.handleSubmit(
|
||||
onSubmitAdvanced
|
||||
)}
|
||||
id="advanced-settings-form"
|
||||
>
|
||||
<FormLabel>
|
||||
TLS Server Name (optional)
|
||||
</FormLabel>
|
||||
<FormField
|
||||
control={advancedForm.control}
|
||||
name="tlsServerName"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<Input {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormLabel>
|
||||
Custom Host Header (optional)
|
||||
</FormLabel>
|
||||
<FormField
|
||||
control={advancedForm.control}
|
||||
name="setHostHeader"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<Input {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</form>
|
||||
</Form>
|
||||
</SettingsSectionForm>
|
||||
</SettingsSectionBody>
|
||||
|
||||
<SettingsSectionFooter>
|
||||
<Button
|
||||
type="submit"
|
||||
loading={saveLoading}
|
||||
disabled={saveLoading}
|
||||
form="advanced-settings-form"
|
||||
>
|
||||
Save Advanced Settings
|
||||
</Button>
|
||||
</SettingsSectionFooter>
|
||||
</SettingsSection>
|
||||
</>
|
||||
)}
|
||||
<SettingsSection>
|
||||
<SettingsSectionHeader>
|
||||
<SettingsSectionTitle>
|
||||
|
|
|
@ -86,8 +86,8 @@ export default async function ResourceLayout(props: ResourceLayoutProps) {
|
|||
href: `/{orgId}/settings/resources/{resourceId}/general`
|
||||
},
|
||||
{
|
||||
title: "Connectivity",
|
||||
href: `/{orgId}/settings/resources/{resourceId}/connectivity`
|
||||
title: "Proxy",
|
||||
href: `/{orgId}/settings/resources/{resourceId}/proxy`
|
||||
}
|
||||
];
|
||||
|
||||
|
|
|
@ -5,6 +5,6 @@ export default async function ResourcePage(props: {
|
|||
}) {
|
||||
const params = await props.params;
|
||||
redirect(
|
||||
`/${params.orgId}/settings/resources/${params.resourceId}/connectivity`
|
||||
`/${params.orgId}/settings/resources/${params.resourceId}/proxy`
|
||||
);
|
||||
}
|
||||
|
|
|
@ -60,17 +60,22 @@ import {
|
|||
SettingsSectionTitle,
|
||||
SettingsSectionDescription,
|
||||
SettingsSectionBody,
|
||||
SettingsSectionFooter
|
||||
SettingsSectionFooter,
|
||||
SettingsSectionForm
|
||||
} from "@app/components/Settings";
|
||||
import { SwitchInput } from "@app/components/SwitchInput";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { isTargetValid } from "@server/lib/validators";
|
||||
import { tlsNameSchema } from "@server/lib/schemas";
|
||||
|
||||
const addTargetSchema = z.object({
|
||||
ip: z.string().refine(isTargetValid),
|
||||
method: z.string().nullable(),
|
||||
port: z.coerce.number().int().positive()
|
||||
// protocol: z.string(),
|
||||
});
|
||||
|
||||
const targetsSettingsSchema = z.object({
|
||||
stickySession: z.boolean()
|
||||
});
|
||||
|
||||
type LocalTarget = Omit<
|
||||
|
@ -81,6 +86,47 @@ type LocalTarget = Omit<
|
|||
"protocol"
|
||||
>;
|
||||
|
||||
const proxySettingsSchema = z.object({
|
||||
setHostHeader: z
|
||||
.string()
|
||||
.optional()
|
||||
.refine(
|
||||
(data) => {
|
||||
if (data) {
|
||||
return tlsNameSchema.safeParse(data).success;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
{
|
||||
message:
|
||||
"Invalid custom Host Header value. Use domain name format, or save empty to unset custom Host Header."
|
||||
}
|
||||
)
|
||||
});
|
||||
|
||||
const tlsSettingsSchema = z.object({
|
||||
ssl: z.boolean(),
|
||||
tlsServerName: z
|
||||
.string()
|
||||
.optional()
|
||||
.refine(
|
||||
(data) => {
|
||||
if (data) {
|
||||
return tlsNameSchema.safeParse(data).success;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
{
|
||||
message:
|
||||
"Invalid TLS Server Name. Use domain name format, or save empty to remove the TLS Server Name."
|
||||
}
|
||||
)
|
||||
});
|
||||
|
||||
type ProxySettingsValues = z.infer<typeof proxySettingsSchema>;
|
||||
type TlsSettingsValues = z.infer<typeof tlsSettingsSchema>;
|
||||
type TargetsSettingsValues = z.infer<typeof targetsSettingsSchema>;
|
||||
|
||||
export default function ReverseProxyTargets(props: {
|
||||
params: Promise<{ resourceId: number }>;
|
||||
}) {
|
||||
|
@ -93,10 +139,10 @@ export default function ReverseProxyTargets(props: {
|
|||
const [targets, setTargets] = useState<LocalTarget[]>([]);
|
||||
const [site, setSite] = useState<GetSiteResponse>();
|
||||
const [targetsToRemove, setTargetsToRemove] = useState<number[]>([]);
|
||||
const [sslEnabled, setSslEnabled] = useState(resource.ssl);
|
||||
const [stickySession, setStickySession] = useState(resource.stickySession);
|
||||
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [httpsTlsLoading, setHttpsTlsLoading] = useState(false);
|
||||
const [targetsLoading, setTargetsLoading] = useState(false);
|
||||
const [proxySettingsLoading, setProxySettingsLoading] = useState(false);
|
||||
|
||||
const [pageLoading, setPageLoading] = useState(true);
|
||||
const router = useRouter();
|
||||
|
@ -110,6 +156,28 @@ export default function ReverseProxyTargets(props: {
|
|||
} as z.infer<typeof addTargetSchema>
|
||||
});
|
||||
|
||||
const tlsSettingsForm = useForm<TlsSettingsValues>({
|
||||
resolver: zodResolver(tlsSettingsSchema),
|
||||
defaultValues: {
|
||||
ssl: resource.ssl,
|
||||
tlsServerName: resource.tlsServerName || ""
|
||||
}
|
||||
});
|
||||
|
||||
const proxySettingsForm = useForm<ProxySettingsValues>({
|
||||
resolver: zodResolver(proxySettingsSchema),
|
||||
defaultValues: {
|
||||
setHostHeader: resource.setHostHeader || ""
|
||||
}
|
||||
});
|
||||
|
||||
const targetsSettingsForm = useForm<TargetsSettingsValues>({
|
||||
resolver: zodResolver(targetsSettingsSchema),
|
||||
defaultValues: {
|
||||
stickySession: resource.stickySession
|
||||
}
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const fetchTargets = async () => {
|
||||
try {
|
||||
|
@ -230,13 +298,12 @@ export default function ReverseProxyTargets(props: {
|
|||
|
||||
async function saveTargets() {
|
||||
try {
|
||||
setLoading(true);
|
||||
setTargetsLoading(true);
|
||||
|
||||
for (let target of targets) {
|
||||
const data = {
|
||||
ip: target.ip,
|
||||
port: target.port,
|
||||
// protocol: target.protocol,
|
||||
method: target.method,
|
||||
enabled: target.enabled
|
||||
};
|
||||
|
@ -249,27 +316,22 @@ export default function ReverseProxyTargets(props: {
|
|||
} else if (target.updated) {
|
||||
await api.post(`/target/${target.targetId}`, data);
|
||||
}
|
||||
|
||||
setTargets([
|
||||
...targets.map((t) => {
|
||||
let res = {
|
||||
...t,
|
||||
new: false,
|
||||
updated: false
|
||||
};
|
||||
return res;
|
||||
})
|
||||
]);
|
||||
}
|
||||
|
||||
for (const targetId of targetsToRemove) {
|
||||
await api.delete(`/target/${targetId}`);
|
||||
setTargets(targets.filter((t) => t.targetId !== targetId));
|
||||
}
|
||||
|
||||
// Save sticky session setting
|
||||
const stickySessionData = targetsSettingsForm.getValues();
|
||||
await api.post(`/resource/${params.resourceId}`, {
|
||||
stickySession: stickySessionData.stickySession
|
||||
});
|
||||
updateResource({ stickySession: stickySessionData.stickySession });
|
||||
|
||||
toast({
|
||||
title: "Targets updated",
|
||||
description: "Targets updated successfully"
|
||||
description: "Targets and settings updated successfully"
|
||||
});
|
||||
|
||||
setTargetsToRemove([]);
|
||||
|
@ -278,72 +340,75 @@ export default function ReverseProxyTargets(props: {
|
|||
console.error(err);
|
||||
toast({
|
||||
variant: "destructive",
|
||||
title: "Operation failed",
|
||||
title: "Failed to update targets",
|
||||
description: formatAxiosError(
|
||||
err,
|
||||
"An error occurred during the save operation"
|
||||
"An error occurred while updating targets"
|
||||
)
|
||||
});
|
||||
} finally {
|
||||
setTargetsLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
setLoading(false);
|
||||
}
|
||||
|
||||
async function saveSsl(val: boolean) {
|
||||
const res = await api
|
||||
.post(`/resource/${params.resourceId}`, {
|
||||
ssl: val
|
||||
})
|
||||
.catch((err) => {
|
||||
async function saveTlsSettings(data: TlsSettingsValues) {
|
||||
try {
|
||||
setHttpsTlsLoading(true);
|
||||
await api.post(`/resource/${params.resourceId}`, {
|
||||
ssl: data.ssl,
|
||||
tlsServerName: data.tlsServerName || undefined
|
||||
});
|
||||
updateResource({
|
||||
...resource,
|
||||
ssl: data.ssl,
|
||||
tlsServerName: data.tlsServerName || undefined
|
||||
});
|
||||
toast({
|
||||
title: "TLS settings updated",
|
||||
description: "Your TLS settings have been updated successfully"
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
toast({
|
||||
variant: "destructive",
|
||||
title: "Failed to update SSL configuration",
|
||||
title: "Failed to update TLS settings",
|
||||
description: formatAxiosError(
|
||||
err,
|
||||
"An error occurred while updating the SSL configuration"
|
||||
"An error occurred while updating TLS settings"
|
||||
)
|
||||
});
|
||||
} finally {
|
||||
setHttpsTlsLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
async function saveProxySettings(data: ProxySettingsValues) {
|
||||
try {
|
||||
setProxySettingsLoading(true);
|
||||
await api.post(`/resource/${params.resourceId}`, {
|
||||
setHostHeader: data.setHostHeader || undefined
|
||||
});
|
||||
updateResource({
|
||||
...resource,
|
||||
setHostHeader: data.setHostHeader || undefined
|
||||
});
|
||||
|
||||
if (res && res.status === 200) {
|
||||
setSslEnabled(val);
|
||||
updateResource({ ssl: val });
|
||||
|
||||
toast({
|
||||
title: "SSL Configuration",
|
||||
description: "SSL configuration updated successfully"
|
||||
title: "Proxy settings updated",
|
||||
description:
|
||||
"Your proxy settings have been updated successfully"
|
||||
});
|
||||
router.refresh();
|
||||
}
|
||||
}
|
||||
|
||||
async function saveStickySession(val: boolean) {
|
||||
const res = await api
|
||||
.post(`/resource/${params.resourceId}`, {
|
||||
stickySession: val
|
||||
})
|
||||
.catch((err) => {
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
toast({
|
||||
variant: "destructive",
|
||||
title: "Failed to update sticky session configuration",
|
||||
title: "Failed to update proxy settings",
|
||||
description: formatAxiosError(
|
||||
err,
|
||||
"An error occurred while updating the sticky session configuration"
|
||||
"An error occurred while updating proxy settings"
|
||||
)
|
||||
});
|
||||
});
|
||||
|
||||
if (res && res.status === 200) {
|
||||
setStickySession(val);
|
||||
updateResource({ stickySession: val });
|
||||
|
||||
toast({
|
||||
title: "Sticky Session Configuration",
|
||||
description: "Sticky session configuration updated successfully"
|
||||
});
|
||||
router.refresh();
|
||||
} finally {
|
||||
setProxySettingsLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -486,46 +551,128 @@ export default function ReverseProxyTargets(props: {
|
|||
<SettingsSection>
|
||||
<SettingsSectionHeader>
|
||||
<SettingsSectionTitle>
|
||||
Advanced Configuration
|
||||
HTTPS & TLS Settings
|
||||
</SettingsSectionTitle>
|
||||
<SettingsSectionDescription>
|
||||
Configure advanced settings for your resource
|
||||
Configure TLS settings for your resource
|
||||
</SettingsSectionDescription>
|
||||
</SettingsSectionHeader>
|
||||
<SettingsSectionBody>
|
||||
{targets.length >= 2 && (
|
||||
<SwitchInput
|
||||
id="sticky-toggle"
|
||||
label="Enable Sticky Sessions"
|
||||
description="Keep users on the same backend target for their entire session. Useful for applications like VNC that require persistent connections."
|
||||
defaultChecked={resource.stickySession}
|
||||
onCheckedChange={async (val) => {
|
||||
await saveStickySession(val);
|
||||
}}
|
||||
/>
|
||||
<SettingsSectionForm>
|
||||
<Form {...tlsSettingsForm}>
|
||||
<form
|
||||
onSubmit={tlsSettingsForm.handleSubmit(
|
||||
saveTlsSettings
|
||||
)}
|
||||
className="space-y-4"
|
||||
id="tls-settings-form"
|
||||
>
|
||||
<FormField
|
||||
control={tlsSettingsForm.control}
|
||||
name="ssl"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<SwitchInput
|
||||
id="ssl-toggle"
|
||||
label="Enable SSL (https)"
|
||||
defaultChecked={resource.ssl}
|
||||
onCheckedChange={async (val) => {
|
||||
await saveSsl(val);
|
||||
defaultChecked={
|
||||
field.value
|
||||
}
|
||||
onCheckedChange={(
|
||||
val
|
||||
) => {
|
||||
field.onChange(val);
|
||||
}}
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={tlsSettingsForm.control}
|
||||
name="tlsServerName"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>
|
||||
TLS Server Name (SNI)
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} />
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
The TLS Server Name to use
|
||||
for SNI. Leave empty to use
|
||||
the default.
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</form>
|
||||
</Form>
|
||||
</SettingsSectionForm>
|
||||
</SettingsSectionBody>
|
||||
<SettingsSectionFooter>
|
||||
<Button
|
||||
type="submit"
|
||||
loading={httpsTlsLoading}
|
||||
form="tls-settings-form"
|
||||
>
|
||||
Save HTTPS & TLS Settings
|
||||
</Button>
|
||||
</SettingsSectionFooter>
|
||||
</SettingsSection>
|
||||
)}
|
||||
{/* Targets Section */}
|
||||
|
||||
<SettingsSection>
|
||||
<SettingsSectionHeader>
|
||||
<SettingsSectionTitle>
|
||||
Target Configuration
|
||||
Targets Configuration
|
||||
</SettingsSectionTitle>
|
||||
<SettingsSectionDescription>
|
||||
Set up targets to route traffic to your services
|
||||
</SettingsSectionDescription>
|
||||
</SettingsSectionHeader>
|
||||
<SettingsSectionBody>
|
||||
<SettingsSectionForm>
|
||||
<Form {...targetsSettingsForm}>
|
||||
<form
|
||||
onSubmit={targetsSettingsForm.handleSubmit(
|
||||
saveTargets
|
||||
)}
|
||||
className="space-y-4"
|
||||
id="targets-settings-form"
|
||||
>
|
||||
{targets.length >= 2 && (
|
||||
<FormField
|
||||
control={targetsSettingsForm.control}
|
||||
name="stickySession"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<SwitchInput
|
||||
id="sticky-toggle"
|
||||
label="Enable Sticky Sessions"
|
||||
description="Keep connections on the same backend target for their entire session."
|
||||
defaultChecked={
|
||||
field.value
|
||||
}
|
||||
onCheckedChange={(
|
||||
val
|
||||
) => {
|
||||
field.onChange(val);
|
||||
}}
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
</form>
|
||||
</Form>
|
||||
</SettingsSectionForm>
|
||||
|
||||
<Form {...addTargetForm}>
|
||||
<form
|
||||
onSubmit={addTargetForm.handleSubmit(addTarget)}
|
||||
|
@ -670,13 +817,70 @@ export default function ReverseProxyTargets(props: {
|
|||
<SettingsSectionFooter>
|
||||
<Button
|
||||
onClick={saveTargets}
|
||||
loading={loading}
|
||||
disabled={loading}
|
||||
loading={targetsLoading}
|
||||
disabled={targetsLoading}
|
||||
form="targets-settings-form"
|
||||
>
|
||||
Save Targets
|
||||
</Button>
|
||||
</SettingsSectionFooter>
|
||||
</SettingsSection>
|
||||
|
||||
{resource.http && (
|
||||
<SettingsSection>
|
||||
<SettingsSectionHeader>
|
||||
<SettingsSectionTitle>
|
||||
Additional Proxy Settings
|
||||
</SettingsSectionTitle>
|
||||
<SettingsSectionDescription>
|
||||
Configure how your resource handles proxy settings
|
||||
</SettingsSectionDescription>
|
||||
</SettingsSectionHeader>
|
||||
<SettingsSectionBody>
|
||||
<SettingsSectionForm>
|
||||
<Form {...proxySettingsForm}>
|
||||
<form
|
||||
onSubmit={proxySettingsForm.handleSubmit(
|
||||
saveProxySettings
|
||||
)}
|
||||
className="space-y-4"
|
||||
id="proxy-settings-form"
|
||||
>
|
||||
<FormField
|
||||
control={proxySettingsForm.control}
|
||||
name="setHostHeader"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>
|
||||
Custom Host Header
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} />
|
||||
</FormControl>
|
||||
<FormDescription>
|
||||
The Host header to set when
|
||||
proxying requests. Leave
|
||||
empty to use the default.
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</form>
|
||||
</Form>
|
||||
</SettingsSectionForm>
|
||||
</SettingsSectionBody>
|
||||
<SettingsSectionFooter>
|
||||
<Button
|
||||
type="submit"
|
||||
loading={proxySettingsLoading}
|
||||
form="proxy-settings-form"
|
||||
>
|
||||
Save Proxy Settings
|
||||
</Button>
|
||||
</SettingsSectionFooter>
|
||||
</SettingsSection>
|
||||
)}
|
||||
</SettingsContainer>
|
||||
);
|
||||
}
|
|
@ -227,11 +227,11 @@ export default function CreateSiteForm({
|
|||
mbIn:
|
||||
data.type == "wireguard" || data.type == "newt"
|
||||
? "0 MB"
|
||||
: "--",
|
||||
: "-",
|
||||
mbOut:
|
||||
data.type == "wireguard" || data.type == "newt"
|
||||
? "0 MB"
|
||||
: "--",
|
||||
: "-",
|
||||
orgId: orgId as string,
|
||||
type: data.type as any,
|
||||
online: false
|
||||
|
|
|
@ -163,7 +163,7 @@ export default function SitesTable({ sites, orgId }: SitesTableProps) {
|
|||
);
|
||||
}
|
||||
} else {
|
||||
return <span>--</span>;
|
||||
return <span>-</span>;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
@ -134,7 +134,7 @@ export default function GeneralPage() {
|
|||
loading={loading}
|
||||
disabled={loading}
|
||||
>
|
||||
Save Settings
|
||||
Save General Settings
|
||||
</Button>
|
||||
</SettingsSectionFooter>
|
||||
</SettingsSection>
|
||||
|
|
|
@ -25,7 +25,7 @@ export default async function SitesPage(props: SitesPageProps) {
|
|||
|
||||
function formatSize(mb: number, type: string): string {
|
||||
if (type === "local") {
|
||||
return "--"; // because we are not able to track the data use in a local site right now
|
||||
return "-"; // because we are not able to track the data use in a local site right now
|
||||
}
|
||||
if (mb >= 1024 * 1024) {
|
||||
return `${(mb / (1024 * 1024)).toFixed(2)} TB`;
|
||||
|
|
|
@ -499,7 +499,7 @@ export default function GeneralPage() {
|
|||
loading={loading}
|
||||
disabled={loading}
|
||||
>
|
||||
Save Settings
|
||||
Save General Settings
|
||||
</Button>
|
||||
</div>
|
||||
</>
|
||||
|
|
|
@ -37,8 +37,8 @@ export function Breadcrumbs() {
|
|||
// label = "Roles";
|
||||
// } else if (segment === "invitations") {
|
||||
// label = "Invitations";
|
||||
// } else if (segment === "connectivity") {
|
||||
// label = "Connectivity";
|
||||
// } else if (segment === "proxy") {
|
||||
// label = "proxy";
|
||||
// } else if (segment === "authentication") {
|
||||
// label = "Authentication";
|
||||
// }
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue