mirror of
https://github.com/Dokploy/dokploy
synced 2025-06-26 18:27:59 +00:00
Merge pull request #486 from Dokploy/333-add-flexible-wwwnon-www-redirect-option
#333 add flexible www/non-www redirect option
This commit is contained in:
@@ -19,6 +19,15 @@ import {
|
|||||||
FormMessage,
|
FormMessage,
|
||||||
} from "@/components/ui/form";
|
} from "@/components/ui/form";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
|
import { Label } from "@/components/ui/label";
|
||||||
|
import {
|
||||||
|
Select,
|
||||||
|
SelectContent,
|
||||||
|
SelectItem,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectValue,
|
||||||
|
} from "@/components/ui/select";
|
||||||
|
import { Separator } from "@/components/ui/separator";
|
||||||
import { Switch } from "@/components/ui/switch";
|
import { Switch } from "@/components/ui/switch";
|
||||||
import { api } from "@/utils/api";
|
import { api } from "@/utils/api";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
@@ -36,6 +45,36 @@ const AddRedirectchema = z.object({
|
|||||||
|
|
||||||
type AddRedirect = z.infer<typeof AddRedirectchema>;
|
type AddRedirect = z.infer<typeof AddRedirectchema>;
|
||||||
|
|
||||||
|
// Default presets
|
||||||
|
const redirectPresets = [
|
||||||
|
// {
|
||||||
|
// label: "Allow www & non-www.",
|
||||||
|
// redirect: {
|
||||||
|
// regex: "",
|
||||||
|
// permanent: false,
|
||||||
|
// replacement: "",
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
{
|
||||||
|
id: "to-www",
|
||||||
|
label: "Redirect to www",
|
||||||
|
redirect: {
|
||||||
|
regex: "^https?://(?:www.)?(.+)",
|
||||||
|
permanent: true,
|
||||||
|
replacement: "https://www.$${1}",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "to-non-www",
|
||||||
|
label: "Redirect to non-www",
|
||||||
|
redirect: {
|
||||||
|
regex: "^https?://www.(.+)",
|
||||||
|
permanent: true,
|
||||||
|
replacement: "https://$${1}",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
applicationId: string;
|
applicationId: string;
|
||||||
children?: React.ReactNode;
|
children?: React.ReactNode;
|
||||||
@@ -43,9 +82,10 @@ interface Props {
|
|||||||
|
|
||||||
export const AddRedirect = ({
|
export const AddRedirect = ({
|
||||||
applicationId,
|
applicationId,
|
||||||
children = <PlusIcon className="h-4 w-4" />,
|
children = <PlusIcon className="w-4 h-4" />,
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
|
const [presetSelected, setPresetSelected] = useState("");
|
||||||
const utils = api.useUtils();
|
const utils = api.useUtils();
|
||||||
|
|
||||||
const { mutateAsync, isLoading, error, isError } =
|
const { mutateAsync, isLoading, error, isError } =
|
||||||
@@ -81,19 +121,36 @@ export const AddRedirect = ({
|
|||||||
await utils.application.readTraefikConfig.invalidate({
|
await utils.application.readTraefikConfig.invalidate({
|
||||||
applicationId,
|
applicationId,
|
||||||
});
|
});
|
||||||
setIsOpen(false);
|
onDialogToggle(false);
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
toast.error("Error to create the redirect");
|
toast.error("Error to create the redirect");
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const onDialogToggle = (open: boolean) => {
|
||||||
|
setIsOpen(open);
|
||||||
|
// commented for the moment because not reseting the form if accidentally closed the dialog can be considered as a feature instead of a bug
|
||||||
|
// setPresetSelected("");
|
||||||
|
// form.reset();
|
||||||
|
};
|
||||||
|
|
||||||
|
const onPresetSelect = (presetId: string) => {
|
||||||
|
const redirectPreset = redirectPresets.find(
|
||||||
|
(preset) => preset.id === presetId,
|
||||||
|
)?.redirect;
|
||||||
|
if (!redirectPreset) return;
|
||||||
|
const { regex, permanent, replacement } = redirectPreset;
|
||||||
|
form.reset({ regex, permanent, replacement }, { keepDefaultValues: true });
|
||||||
|
setPresetSelected(presetId);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open={isOpen} onOpenChange={setIsOpen}>
|
<Dialog open={isOpen} onOpenChange={onDialogToggle}>
|
||||||
<DialogTrigger asChild>
|
<DialogTrigger asChild>
|
||||||
<Button>{children}</Button>
|
<Button>{children}</Button>
|
||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-lg">
|
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-lg">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>Redirects</DialogTitle>
|
<DialogTitle>Redirects</DialogTitle>
|
||||||
<DialogDescription>
|
<DialogDescription>
|
||||||
@@ -102,6 +159,24 @@ export const AddRedirect = ({
|
|||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
{isError && <AlertBlock type="error">{error?.message}</AlertBlock>}
|
{isError && <AlertBlock type="error">{error?.message}</AlertBlock>}
|
||||||
|
|
||||||
|
<div className="md:col-span-2">
|
||||||
|
<Label>Presets</Label>
|
||||||
|
<Select onValueChange={onPresetSelect} value={presetSelected}>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="No preset selected" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
{redirectPresets.map((preset) => (
|
||||||
|
<SelectItem key={preset.label} value={preset.id}>
|
||||||
|
{preset.label}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Separator />
|
||||||
|
|
||||||
<Form {...form}>
|
<Form {...form}>
|
||||||
<form
|
<form
|
||||||
id="hook-form-add-redirect"
|
id="hook-form-add-redirect"
|
||||||
@@ -142,7 +217,7 @@ export const AddRedirect = ({
|
|||||||
control={form.control}
|
control={form.control}
|
||||||
name="permanent"
|
name="permanent"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem className="mt-4 flex flex-row items-center justify-between rounded-lg border p-3 shadow-sm">
|
<FormItem className="flex flex-row items-center justify-between p-3 mt-4 border rounded-lg shadow-sm">
|
||||||
<div className="space-y-0.5">
|
<div className="space-y-0.5">
|
||||||
<FormLabel>Permanent</FormLabel>
|
<FormLabel>Permanent</FormLabel>
|
||||||
<FormDescription>
|
<FormDescription>
|
||||||
|
|||||||
@@ -140,7 +140,7 @@ export const AddDomain = ({
|
|||||||
<DialogTrigger className="" asChild>
|
<DialogTrigger className="" asChild>
|
||||||
{children}
|
{children}
|
||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-2xl">
|
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-2xl">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>Domain</DialogTitle>
|
<DialogTitle>Domain</DialogTitle>
|
||||||
<DialogDescription>{dictionary.dialogDescription}</DialogDescription>
|
<DialogDescription>{dictionary.dialogDescription}</DialogDescription>
|
||||||
@@ -241,6 +241,29 @@ export const AddDomain = ({
|
|||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="https"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem className="flex flex-row items-center justify-between p-3 mt-4 border rounded-lg shadow-sm">
|
||||||
|
<div className="space-y-0.5">
|
||||||
|
<FormLabel>HTTPS</FormLabel>
|
||||||
|
<FormDescription>
|
||||||
|
Automatically provision SSL Certificate.
|
||||||
|
</FormDescription>
|
||||||
|
<FormMessage />
|
||||||
|
</div>
|
||||||
|
<FormControl>
|
||||||
|
<Switch
|
||||||
|
checked={field.value}
|
||||||
|
onCheckedChange={field.onChange}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
{form.getValues().https && (
|
{form.getValues().https && (
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
@@ -270,28 +293,6 @@ export const AddDomain = ({
|
|||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="https"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem className="mt-4 flex flex-row items-center justify-between rounded-lg border p-3 shadow-sm">
|
|
||||||
<div className="space-y-0.5">
|
|
||||||
<FormLabel>HTTPS</FormLabel>
|
|
||||||
<FormDescription>
|
|
||||||
Automatically provision SSL Certificate.
|
|
||||||
</FormDescription>
|
|
||||||
<FormMessage />
|
|
||||||
</div>
|
|
||||||
<FormControl>
|
|
||||||
<Switch
|
|
||||||
checked={field.value}
|
|
||||||
onCheckedChange={field.onChange}
|
|
||||||
/>
|
|
||||||
</FormControl>
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -161,7 +161,7 @@ export const AddDomainCompose = ({
|
|||||||
<DialogTrigger className="" asChild>
|
<DialogTrigger className="" asChild>
|
||||||
{children}
|
{children}
|
||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-2xl">
|
<DialogContent className="max-h-screen overflow-y-auto sm:max-w-2xl">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>Domain</DialogTitle>
|
<DialogTitle>Domain</DialogTitle>
|
||||||
<DialogDescription>{dictionary.dialogDescription}</DialogDescription>
|
<DialogDescription>{dictionary.dialogDescription}</DialogDescription>
|
||||||
@@ -190,7 +190,7 @@ export const AddDomainCompose = ({
|
|||||||
{errorServices?.message}
|
{errorServices?.message}
|
||||||
</AlertBlock>
|
</AlertBlock>
|
||||||
)}
|
)}
|
||||||
<div className="flex flex-row gap-4 w-full items-end">
|
<div className="flex flex-row items-end w-full gap-4">
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="serviceName"
|
name="serviceName"
|
||||||
@@ -377,6 +377,29 @@ export const AddDomainCompose = ({
|
|||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="https"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem className="flex flex-row items-center justify-between p-3 mt-4 border rounded-lg shadow-sm">
|
||||||
|
<div className="space-y-0.5">
|
||||||
|
<FormLabel>HTTPS</FormLabel>
|
||||||
|
<FormDescription>
|
||||||
|
Automatically provision SSL Certificate.
|
||||||
|
</FormDescription>
|
||||||
|
<FormMessage />
|
||||||
|
</div>
|
||||||
|
<FormControl>
|
||||||
|
<Switch
|
||||||
|
checked={field.value}
|
||||||
|
onCheckedChange={field.onChange}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
{https && (
|
{https && (
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
@@ -406,28 +429,6 @@ export const AddDomainCompose = ({
|
|||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<FormField
|
|
||||||
control={form.control}
|
|
||||||
name="https"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem className="mt-4 flex flex-row items-center justify-between rounded-lg border p-3 shadow-sm">
|
|
||||||
<div className="space-y-0.5">
|
|
||||||
<FormLabel>HTTPS</FormLabel>
|
|
||||||
<FormDescription>
|
|
||||||
Automatically provision SSL Certificate.
|
|
||||||
</FormDescription>
|
|
||||||
<FormMessage />
|
|
||||||
</div>
|
|
||||||
<FormControl>
|
|
||||||
<Switch
|
|
||||||
checked={field.value}
|
|
||||||
onCheckedChange={field.onChange}
|
|
||||||
/>
|
|
||||||
</FormControl>
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
Reference in New Issue
Block a user