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({
|
.object({
|
||||||
name: z.string().min(1).max(255).optional(),
|
name: z.string().min(1).max(255).optional(),
|
||||||
proxyPort: z.number().int().min(1).max(65535).optional(),
|
proxyPort: z.number().int().min(1).max(65535).optional(),
|
||||||
|
stickySession: z.boolean().optional(),
|
||||||
enabled: z.boolean().optional()
|
enabled: z.boolean().optional()
|
||||||
})
|
})
|
||||||
.strict()
|
.strict()
|
||||||
|
|
|
@ -41,7 +41,7 @@ export async function traefikConfigProvider(
|
||||||
orgId: orgs.orgId
|
orgId: orgs.orgId
|
||||||
},
|
},
|
||||||
enabled: resources.enabled,
|
enabled: resources.enabled,
|
||||||
stickySession: resources.stickySessionk,
|
stickySession: resources.stickySession,
|
||||||
tlsServerName: resources.tlsServerName,
|
tlsServerName: resources.tlsServerName,
|
||||||
setHostHeader: resources.setHostHeader
|
setHostHeader: resources.setHostHeader
|
||||||
})
|
})
|
||||||
|
@ -288,7 +288,7 @@ export async function traefikConfigProvider(
|
||||||
? {
|
? {
|
||||||
sticky: {
|
sticky: {
|
||||||
cookie: {
|
cookie: {
|
||||||
name: "pangolin_sticky",
|
name: "p_sticky", // TODO: make this configurable via config.yml like other cookies
|
||||||
secure: resource.ssl,
|
secure: resource.ssl,
|
||||||
httpOnly: true
|
httpOnly: true
|
||||||
}
|
}
|
||||||
|
|
|
@ -234,7 +234,7 @@ export default function GeneralPage() {
|
||||||
loading={loadingSave}
|
loading={loadingSave}
|
||||||
disabled={loadingSave}
|
disabled={loadingSave}
|
||||||
>
|
>
|
||||||
Save Settings
|
Save General Settings
|
||||||
</Button>
|
</Button>
|
||||||
</SettingsSectionFooter>
|
</SettingsSectionFooter>
|
||||||
</SettingsSection>
|
</SettingsSection>
|
||||||
|
|
|
@ -57,7 +57,8 @@ export default function SitesTable({ resources, orgId }: ResourcesTableProps) {
|
||||||
const api = createApiClient(useEnvContext());
|
const api = createApiClient(useEnvContext());
|
||||||
|
|
||||||
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
|
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
|
||||||
const [selectedResource, setSelectedResource] = useState<ResourceRow | null>();
|
const [selectedResource, setSelectedResource] =
|
||||||
|
useState<ResourceRow | null>();
|
||||||
|
|
||||||
const deleteResource = (resourceId: number) => {
|
const deleteResource = (resourceId: number) => {
|
||||||
api.delete(`/resource/${resourceId}`)
|
api.delete(`/resource/${resourceId}`)
|
||||||
|
@ -238,7 +239,7 @@ export default function SitesTable({ resources, orgId }: ResourcesTableProps) {
|
||||||
<span>Not Protected</span>
|
<span>Not Protected</span>
|
||||||
</span>
|
</span>
|
||||||
) : (
|
) : (
|
||||||
<span>--</span>
|
<span>-</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -109,42 +109,8 @@ const TransferFormSchema = z.object({
|
||||||
siteId: z.number()
|
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 GeneralFormValues = z.infer<typeof GeneralFormSchema>;
|
||||||
type TransferFormValues = z.infer<typeof TransferFormSchema>;
|
type TransferFormValues = z.infer<typeof TransferFormSchema>;
|
||||||
type AdvancedFormValues = z.infer<typeof AdvancedFormSchema>;
|
|
||||||
|
|
||||||
export default function GeneralForm() {
|
export default function GeneralForm() {
|
||||||
const [formKey, setFormKey] = useState(0);
|
const [formKey, setFormKey] = useState(0);
|
||||||
|
@ -185,20 +151,6 @@ export default function GeneralForm() {
|
||||||
mode: "onChange"
|
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>({
|
const transferForm = useForm<TransferFormValues>({
|
||||||
resolver: zodResolver(TransferFormSchema),
|
resolver: zodResolver(TransferFormSchema),
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
|
@ -327,46 +279,6 @@ export default function GeneralForm() {
|
||||||
setTransferLoading(false);
|
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) {
|
async function toggleResourceEnabled(val: boolean) {
|
||||||
const res = await api
|
const res = await api
|
||||||
.post<AxiosResponse<UpdateResourceResponse>>(
|
.post<AxiosResponse<UpdateResourceResponse>>(
|
||||||
|
@ -684,82 +596,11 @@ export default function GeneralForm() {
|
||||||
disabled={saveLoading}
|
disabled={saveLoading}
|
||||||
form="general-settings-form"
|
form="general-settings-form"
|
||||||
>
|
>
|
||||||
Save Settings
|
Save General Settings
|
||||||
</Button>
|
</Button>
|
||||||
</SettingsSectionFooter>
|
</SettingsSectionFooter>
|
||||||
</SettingsSection>
|
</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>
|
<SettingsSection>
|
||||||
<SettingsSectionHeader>
|
<SettingsSectionHeader>
|
||||||
<SettingsSectionTitle>
|
<SettingsSectionTitle>
|
||||||
|
|
|
@ -86,8 +86,8 @@ export default async function ResourceLayout(props: ResourceLayoutProps) {
|
||||||
href: `/{orgId}/settings/resources/{resourceId}/general`
|
href: `/{orgId}/settings/resources/{resourceId}/general`
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Connectivity",
|
title: "Proxy",
|
||||||
href: `/{orgId}/settings/resources/{resourceId}/connectivity`
|
href: `/{orgId}/settings/resources/{resourceId}/proxy`
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,6 @@ export default async function ResourcePage(props: {
|
||||||
}) {
|
}) {
|
||||||
const params = await props.params;
|
const params = await props.params;
|
||||||
redirect(
|
redirect(
|
||||||
`/${params.orgId}/settings/resources/${params.resourceId}/connectivity`
|
`/${params.orgId}/settings/resources/${params.resourceId}/proxy`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -60,17 +60,22 @@ import {
|
||||||
SettingsSectionTitle,
|
SettingsSectionTitle,
|
||||||
SettingsSectionDescription,
|
SettingsSectionDescription,
|
||||||
SettingsSectionBody,
|
SettingsSectionBody,
|
||||||
SettingsSectionFooter
|
SettingsSectionFooter,
|
||||||
|
SettingsSectionForm
|
||||||
} from "@app/components/Settings";
|
} from "@app/components/Settings";
|
||||||
import { SwitchInput } from "@app/components/SwitchInput";
|
import { SwitchInput } from "@app/components/SwitchInput";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import { isTargetValid } from "@server/lib/validators";
|
import { isTargetValid } from "@server/lib/validators";
|
||||||
|
import { tlsNameSchema } from "@server/lib/schemas";
|
||||||
|
|
||||||
const addTargetSchema = z.object({
|
const addTargetSchema = z.object({
|
||||||
ip: z.string().refine(isTargetValid),
|
ip: z.string().refine(isTargetValid),
|
||||||
method: z.string().nullable(),
|
method: z.string().nullable(),
|
||||||
port: z.coerce.number().int().positive()
|
port: z.coerce.number().int().positive()
|
||||||
// protocol: z.string(),
|
});
|
||||||
|
|
||||||
|
const targetsSettingsSchema = z.object({
|
||||||
|
stickySession: z.boolean()
|
||||||
});
|
});
|
||||||
|
|
||||||
type LocalTarget = Omit<
|
type LocalTarget = Omit<
|
||||||
|
@ -81,6 +86,47 @@ type LocalTarget = Omit<
|
||||||
"protocol"
|
"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: {
|
export default function ReverseProxyTargets(props: {
|
||||||
params: Promise<{ resourceId: number }>;
|
params: Promise<{ resourceId: number }>;
|
||||||
}) {
|
}) {
|
||||||
|
@ -93,10 +139,10 @@ export default function ReverseProxyTargets(props: {
|
||||||
const [targets, setTargets] = useState<LocalTarget[]>([]);
|
const [targets, setTargets] = useState<LocalTarget[]>([]);
|
||||||
const [site, setSite] = useState<GetSiteResponse>();
|
const [site, setSite] = useState<GetSiteResponse>();
|
||||||
const [targetsToRemove, setTargetsToRemove] = useState<number[]>([]);
|
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 [pageLoading, setPageLoading] = useState(true);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
@ -110,6 +156,28 @@ export default function ReverseProxyTargets(props: {
|
||||||
} as z.infer<typeof addTargetSchema>
|
} 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(() => {
|
useEffect(() => {
|
||||||
const fetchTargets = async () => {
|
const fetchTargets = async () => {
|
||||||
try {
|
try {
|
||||||
|
@ -230,13 +298,12 @@ export default function ReverseProxyTargets(props: {
|
||||||
|
|
||||||
async function saveTargets() {
|
async function saveTargets() {
|
||||||
try {
|
try {
|
||||||
setLoading(true);
|
setTargetsLoading(true);
|
||||||
|
|
||||||
for (let target of targets) {
|
for (let target of targets) {
|
||||||
const data = {
|
const data = {
|
||||||
ip: target.ip,
|
ip: target.ip,
|
||||||
port: target.port,
|
port: target.port,
|
||||||
// protocol: target.protocol,
|
|
||||||
method: target.method,
|
method: target.method,
|
||||||
enabled: target.enabled
|
enabled: target.enabled
|
||||||
};
|
};
|
||||||
|
@ -249,27 +316,22 @@ export default function ReverseProxyTargets(props: {
|
||||||
} else if (target.updated) {
|
} else if (target.updated) {
|
||||||
await api.post(`/target/${target.targetId}`, data);
|
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) {
|
for (const targetId of targetsToRemove) {
|
||||||
await api.delete(`/target/${targetId}`);
|
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({
|
toast({
|
||||||
title: "Targets updated",
|
title: "Targets updated",
|
||||||
description: "Targets updated successfully"
|
description: "Targets and settings updated successfully"
|
||||||
});
|
});
|
||||||
|
|
||||||
setTargetsToRemove([]);
|
setTargetsToRemove([]);
|
||||||
|
@ -278,72 +340,75 @@ export default function ReverseProxyTargets(props: {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
toast({
|
toast({
|
||||||
variant: "destructive",
|
variant: "destructive",
|
||||||
title: "Operation failed",
|
title: "Failed to update targets",
|
||||||
description: formatAxiosError(
|
description: formatAxiosError(
|
||||||
err,
|
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) => {
|
|
||||||
console.error(err);
|
|
||||||
toast({
|
|
||||||
variant: "destructive",
|
|
||||||
title: "Failed to update SSL configuration",
|
|
||||||
description: formatAxiosError(
|
|
||||||
err,
|
|
||||||
"An error occurred while updating the SSL configuration"
|
|
||||||
)
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
if (res && res.status === 200) {
|
|
||||||
setSslEnabled(val);
|
|
||||||
updateResource({ ssl: val });
|
|
||||||
|
|
||||||
toast({
|
|
||||||
title: "SSL Configuration",
|
|
||||||
description: "SSL configuration updated successfully"
|
|
||||||
});
|
|
||||||
router.refresh();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function saveStickySession(val: boolean) {
|
async function saveTlsSettings(data: TlsSettingsValues) {
|
||||||
const res = await api
|
try {
|
||||||
.post(`/resource/${params.resourceId}`, {
|
setHttpsTlsLoading(true);
|
||||||
stickySession: val
|
await api.post(`/resource/${params.resourceId}`, {
|
||||||
})
|
ssl: data.ssl,
|
||||||
.catch((err) => {
|
tlsServerName: data.tlsServerName || undefined
|
||||||
console.error(err);
|
});
|
||||||
toast({
|
updateResource({
|
||||||
variant: "destructive",
|
...resource,
|
||||||
title: "Failed to update sticky session configuration",
|
ssl: data.ssl,
|
||||||
description: formatAxiosError(
|
tlsServerName: data.tlsServerName || undefined
|
||||||
err,
|
|
||||||
"An error occurred while updating the sticky session configuration"
|
|
||||||
)
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (res && res.status === 200) {
|
|
||||||
setStickySession(val);
|
|
||||||
updateResource({ stickySession: val });
|
|
||||||
|
|
||||||
toast({
|
toast({
|
||||||
title: "Sticky Session Configuration",
|
title: "TLS settings updated",
|
||||||
description: "Sticky session configuration updated successfully"
|
description: "Your TLS settings have been updated successfully"
|
||||||
});
|
});
|
||||||
router.refresh();
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
toast({
|
||||||
|
variant: "destructive",
|
||||||
|
title: "Failed to update TLS settings",
|
||||||
|
description: formatAxiosError(
|
||||||
|
err,
|
||||||
|
"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
|
||||||
|
});
|
||||||
|
toast({
|
||||||
|
title: "Proxy settings updated",
|
||||||
|
description:
|
||||||
|
"Your proxy settings have been updated successfully"
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
toast({
|
||||||
|
variant: "destructive",
|
||||||
|
title: "Failed to update proxy settings",
|
||||||
|
description: formatAxiosError(
|
||||||
|
err,
|
||||||
|
"An error occurred while updating proxy settings"
|
||||||
|
)
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
setProxySettingsLoading(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -486,46 +551,128 @@ export default function ReverseProxyTargets(props: {
|
||||||
<SettingsSection>
|
<SettingsSection>
|
||||||
<SettingsSectionHeader>
|
<SettingsSectionHeader>
|
||||||
<SettingsSectionTitle>
|
<SettingsSectionTitle>
|
||||||
Advanced Configuration
|
HTTPS & TLS Settings
|
||||||
</SettingsSectionTitle>
|
</SettingsSectionTitle>
|
||||||
<SettingsSectionDescription>
|
<SettingsSectionDescription>
|
||||||
Configure advanced settings for your resource
|
Configure TLS settings for your resource
|
||||||
</SettingsSectionDescription>
|
</SettingsSectionDescription>
|
||||||
</SettingsSectionHeader>
|
</SettingsSectionHeader>
|
||||||
<SettingsSectionBody>
|
<SettingsSectionBody>
|
||||||
{targets.length >= 2 && (
|
<SettingsSectionForm>
|
||||||
<SwitchInput
|
<Form {...tlsSettingsForm}>
|
||||||
id="sticky-toggle"
|
<form
|
||||||
label="Enable Sticky Sessions"
|
onSubmit={tlsSettingsForm.handleSubmit(
|
||||||
description="Keep users on the same backend target for their entire session. Useful for applications like VNC that require persistent connections."
|
saveTlsSettings
|
||||||
defaultChecked={resource.stickySession}
|
)}
|
||||||
onCheckedChange={async (val) => {
|
className="space-y-4"
|
||||||
await saveStickySession(val);
|
id="tls-settings-form"
|
||||||
}}
|
>
|
||||||
/>
|
<FormField
|
||||||
)}
|
control={tlsSettingsForm.control}
|
||||||
<SwitchInput
|
name="ssl"
|
||||||
id="ssl-toggle"
|
render={({ field }) => (
|
||||||
label="Enable SSL (https)"
|
<FormItem>
|
||||||
defaultChecked={resource.ssl}
|
<FormControl>
|
||||||
onCheckedChange={async (val) => {
|
<SwitchInput
|
||||||
await saveSsl(val);
|
id="ssl-toggle"
|
||||||
}}
|
label="Enable SSL (https)"
|
||||||
/>
|
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>
|
</SettingsSectionBody>
|
||||||
|
<SettingsSectionFooter>
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
loading={httpsTlsLoading}
|
||||||
|
form="tls-settings-form"
|
||||||
|
>
|
||||||
|
Save HTTPS & TLS Settings
|
||||||
|
</Button>
|
||||||
|
</SettingsSectionFooter>
|
||||||
</SettingsSection>
|
</SettingsSection>
|
||||||
)}
|
)}
|
||||||
{/* Targets Section */}
|
|
||||||
<SettingsSection>
|
<SettingsSection>
|
||||||
<SettingsSectionHeader>
|
<SettingsSectionHeader>
|
||||||
<SettingsSectionTitle>
|
<SettingsSectionTitle>
|
||||||
Target Configuration
|
Targets Configuration
|
||||||
</SettingsSectionTitle>
|
</SettingsSectionTitle>
|
||||||
<SettingsSectionDescription>
|
<SettingsSectionDescription>
|
||||||
Set up targets to route traffic to your services
|
Set up targets to route traffic to your services
|
||||||
</SettingsSectionDescription>
|
</SettingsSectionDescription>
|
||||||
</SettingsSectionHeader>
|
</SettingsSectionHeader>
|
||||||
<SettingsSectionBody>
|
<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 {...addTargetForm}>
|
||||||
<form
|
<form
|
||||||
onSubmit={addTargetForm.handleSubmit(addTarget)}
|
onSubmit={addTargetForm.handleSubmit(addTarget)}
|
||||||
|
@ -670,13 +817,70 @@ export default function ReverseProxyTargets(props: {
|
||||||
<SettingsSectionFooter>
|
<SettingsSectionFooter>
|
||||||
<Button
|
<Button
|
||||||
onClick={saveTargets}
|
onClick={saveTargets}
|
||||||
loading={loading}
|
loading={targetsLoading}
|
||||||
disabled={loading}
|
disabled={targetsLoading}
|
||||||
|
form="targets-settings-form"
|
||||||
>
|
>
|
||||||
Save Targets
|
Save Targets
|
||||||
</Button>
|
</Button>
|
||||||
</SettingsSectionFooter>
|
</SettingsSectionFooter>
|
||||||
</SettingsSection>
|
</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>
|
</SettingsContainer>
|
||||||
);
|
);
|
||||||
}
|
}
|
|
@ -227,11 +227,11 @@ export default function CreateSiteForm({
|
||||||
mbIn:
|
mbIn:
|
||||||
data.type == "wireguard" || data.type == "newt"
|
data.type == "wireguard" || data.type == "newt"
|
||||||
? "0 MB"
|
? "0 MB"
|
||||||
: "--",
|
: "-",
|
||||||
mbOut:
|
mbOut:
|
||||||
data.type == "wireguard" || data.type == "newt"
|
data.type == "wireguard" || data.type == "newt"
|
||||||
? "0 MB"
|
? "0 MB"
|
||||||
: "--",
|
: "-",
|
||||||
orgId: orgId as string,
|
orgId: orgId as string,
|
||||||
type: data.type as any,
|
type: data.type as any,
|
||||||
online: false
|
online: false
|
||||||
|
|
|
@ -163,7 +163,7 @@ export default function SitesTable({ sites, orgId }: SitesTableProps) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return <span>--</span>;
|
return <span>-</span>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -134,7 +134,7 @@ export default function GeneralPage() {
|
||||||
loading={loading}
|
loading={loading}
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
>
|
>
|
||||||
Save Settings
|
Save General Settings
|
||||||
</Button>
|
</Button>
|
||||||
</SettingsSectionFooter>
|
</SettingsSectionFooter>
|
||||||
</SettingsSection>
|
</SettingsSection>
|
||||||
|
|
|
@ -25,7 +25,7 @@ export default async function SitesPage(props: SitesPageProps) {
|
||||||
|
|
||||||
function formatSize(mb: number, type: string): string {
|
function formatSize(mb: number, type: string): string {
|
||||||
if (type === "local") {
|
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) {
|
if (mb >= 1024 * 1024) {
|
||||||
return `${(mb / (1024 * 1024)).toFixed(2)} TB`;
|
return `${(mb / (1024 * 1024)).toFixed(2)} TB`;
|
||||||
|
|
|
@ -499,7 +499,7 @@ export default function GeneralPage() {
|
||||||
loading={loading}
|
loading={loading}
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
>
|
>
|
||||||
Save Settings
|
Save General Settings
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
|
|
|
@ -37,8 +37,8 @@ export function Breadcrumbs() {
|
||||||
// label = "Roles";
|
// label = "Roles";
|
||||||
// } else if (segment === "invitations") {
|
// } else if (segment === "invitations") {
|
||||||
// label = "Invitations";
|
// label = "Invitations";
|
||||||
// } else if (segment === "connectivity") {
|
// } else if (segment === "proxy") {
|
||||||
// label = "Connectivity";
|
// label = "proxy";
|
||||||
// } else if (segment === "authentication") {
|
// } else if (segment === "authentication") {
|
||||||
// label = "Authentication";
|
// label = "Authentication";
|
||||||
// }
|
// }
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue