|
@@ -9,7 +9,7 @@ import {
|
|
SelectContent,
|
|
SelectContent,
|
|
SelectItem,
|
|
SelectItem,
|
|
SelectTrigger,
|
|
SelectTrigger,
|
|
- SelectValue,
|
|
|
|
|
|
+ SelectValue
|
|
} from "@/components/ui/select";
|
|
} from "@/components/ui/select";
|
|
import { Switch } from "@/components/ui/switch";
|
|
import { Switch } from "@/components/ui/switch";
|
|
import { AxiosResponse } from "axios";
|
|
import { AxiosResponse } from "axios";
|
|
@@ -24,7 +24,7 @@ import {
|
|
FormField,
|
|
FormField,
|
|
FormItem,
|
|
FormItem,
|
|
FormLabel,
|
|
FormLabel,
|
|
- FormMessage,
|
|
|
|
|
|
+ FormMessage
|
|
} from "@app/components/ui/form";
|
|
} from "@app/components/ui/form";
|
|
import { CreateTargetResponse } from "@server/routers/target";
|
|
import { CreateTargetResponse } from "@server/routers/target";
|
|
import {
|
|
import {
|
|
@@ -34,7 +34,7 @@ import {
|
|
getPaginationRowModel,
|
|
getPaginationRowModel,
|
|
getCoreRowModel,
|
|
getCoreRowModel,
|
|
useReactTable,
|
|
useReactTable,
|
|
- flexRender,
|
|
|
|
|
|
+ flexRender
|
|
} from "@tanstack/react-table";
|
|
} from "@tanstack/react-table";
|
|
import {
|
|
import {
|
|
Table,
|
|
Table,
|
|
@@ -42,7 +42,7 @@ import {
|
|
TableCell,
|
|
TableCell,
|
|
TableHead,
|
|
TableHead,
|
|
TableHeader,
|
|
TableHeader,
|
|
- TableRow,
|
|
|
|
|
|
+ TableRow
|
|
} from "@app/components/ui/table";
|
|
} from "@app/components/ui/table";
|
|
import { useToast } from "@app/hooks/useToast";
|
|
import { useToast } from "@app/hooks/useToast";
|
|
import SettingsSectionTitle from "@app/components/SettingsSectionTitle";
|
|
import SettingsSectionTitle from "@app/components/SettingsSectionTitle";
|
|
@@ -59,9 +59,9 @@ const addTargetSchema = z.object({
|
|
port: z
|
|
port: z
|
|
.string()
|
|
.string()
|
|
.refine((val) => !isNaN(Number(val)), {
|
|
.refine((val) => !isNaN(Number(val)), {
|
|
- message: "Port must be a number",
|
|
|
|
|
|
+ message: "Port must be a number"
|
|
})
|
|
})
|
|
- .transform((val) => Number(val)),
|
|
|
|
|
|
+ .transform((val) => Number(val))
|
|
// protocol: z.string(),
|
|
// protocol: z.string(),
|
|
});
|
|
});
|
|
|
|
|
|
@@ -99,16 +99,16 @@ export default function ReverseProxyTargets(props: {
|
|
defaultValues: {
|
|
defaultValues: {
|
|
ip: "",
|
|
ip: "",
|
|
method: "http",
|
|
method: "http",
|
|
- port: "80",
|
|
|
|
|
|
+ port: "80"
|
|
// protocol: "TCP",
|
|
// protocol: "TCP",
|
|
- },
|
|
|
|
|
|
+ }
|
|
});
|
|
});
|
|
|
|
|
|
useEffect(() => {
|
|
useEffect(() => {
|
|
const fetchTargets = async () => {
|
|
const fetchTargets = async () => {
|
|
try {
|
|
try {
|
|
const res = await api.get<AxiosResponse<ListTargetsResponse>>(
|
|
const res = await api.get<AxiosResponse<ListTargetsResponse>>(
|
|
- `/resource/${params.resourceId}/targets`,
|
|
|
|
|
|
+ `/resource/${params.resourceId}/targets`
|
|
);
|
|
);
|
|
|
|
|
|
if (res.status === 200) {
|
|
if (res.status === 200) {
|
|
@@ -121,8 +121,8 @@ export default function ReverseProxyTargets(props: {
|
|
title: "Failed to fetch targets",
|
|
title: "Failed to fetch targets",
|
|
description: formatAxiosError(
|
|
description: formatAxiosError(
|
|
err,
|
|
err,
|
|
- "An error occurred while fetching targets",
|
|
|
|
- ),
|
|
|
|
|
|
+ "An error occurred while fetching targets"
|
|
|
|
+ )
|
|
});
|
|
});
|
|
} finally {
|
|
} finally {
|
|
setPageLoading(false);
|
|
setPageLoading(false);
|
|
@@ -133,7 +133,7 @@ export default function ReverseProxyTargets(props: {
|
|
const fetchSite = async () => {
|
|
const fetchSite = async () => {
|
|
try {
|
|
try {
|
|
const res = await api.get<AxiosResponse<GetSiteResponse>>(
|
|
const res = await api.get<AxiosResponse<GetSiteResponse>>(
|
|
- `/site/${resource.siteId}`,
|
|
|
|
|
|
+ `/site/${resource.siteId}`
|
|
);
|
|
);
|
|
|
|
|
|
if (res.status === 200) {
|
|
if (res.status === 200) {
|
|
@@ -146,27 +146,28 @@ export default function ReverseProxyTargets(props: {
|
|
title: "Failed to fetch resource",
|
|
title: "Failed to fetch resource",
|
|
description: formatAxiosError(
|
|
description: formatAxiosError(
|
|
err,
|
|
err,
|
|
- "An error occurred while fetching resource",
|
|
|
|
- ),
|
|
|
|
|
|
+ "An error occurred while fetching resource"
|
|
|
|
+ )
|
|
});
|
|
});
|
|
}
|
|
}
|
|
- }
|
|
|
|
|
|
+ };
|
|
fetchSite();
|
|
fetchSite();
|
|
}, []);
|
|
}, []);
|
|
|
|
|
|
async function addTarget(data: AddTargetFormValues) {
|
|
async function addTarget(data: AddTargetFormValues) {
|
|
// Check if target with same IP, port and method already exists
|
|
// Check if target with same IP, port and method already exists
|
|
const isDuplicate = targets.some(
|
|
const isDuplicate = targets.some(
|
|
- target => target.ip === data.ip &&
|
|
|
|
- target.port === data.port &&
|
|
|
|
- target.method === data.method
|
|
|
|
|
|
+ (target) =>
|
|
|
|
+ target.ip === data.ip &&
|
|
|
|
+ target.port === data.port &&
|
|
|
|
+ target.method === data.method
|
|
);
|
|
);
|
|
|
|
|
|
if (isDuplicate) {
|
|
if (isDuplicate) {
|
|
toast({
|
|
toast({
|
|
variant: "destructive",
|
|
variant: "destructive",
|
|
title: "Duplicate target",
|
|
title: "Duplicate target",
|
|
- description: "A target with these settings already exists",
|
|
|
|
|
|
+ description: "A target with these settings already exists"
|
|
});
|
|
});
|
|
return;
|
|
return;
|
|
}
|
|
}
|
|
@@ -179,7 +180,7 @@ export default function ReverseProxyTargets(props: {
|
|
toast({
|
|
toast({
|
|
variant: "destructive",
|
|
variant: "destructive",
|
|
title: "Invalid target IP",
|
|
title: "Invalid target IP",
|
|
- description: "Target IP must be within the site subnet",
|
|
|
|
|
|
+ description: "Target IP must be within the site subnet"
|
|
});
|
|
});
|
|
return;
|
|
return;
|
|
}
|
|
}
|
|
@@ -190,7 +191,7 @@ export default function ReverseProxyTargets(props: {
|
|
enabled: true,
|
|
enabled: true,
|
|
targetId: new Date().getTime(),
|
|
targetId: new Date().getTime(),
|
|
new: true,
|
|
new: true,
|
|
- resourceId: resource.resourceId,
|
|
|
|
|
|
+ resourceId: resource.resourceId
|
|
};
|
|
};
|
|
|
|
|
|
setTargets([...targets, newTarget]);
|
|
setTargets([...targets, newTarget]);
|
|
@@ -199,7 +200,7 @@ export default function ReverseProxyTargets(props: {
|
|
|
|
|
|
const removeTarget = (targetId: number) => {
|
|
const removeTarget = (targetId: number) => {
|
|
setTargets([
|
|
setTargets([
|
|
- ...targets.filter((target) => target.targetId !== targetId),
|
|
|
|
|
|
+ ...targets.filter((target) => target.targetId !== targetId)
|
|
]);
|
|
]);
|
|
|
|
|
|
if (!targets.find((target) => target.targetId === targetId)?.new) {
|
|
if (!targets.find((target) => target.targetId === targetId)?.new) {
|
|
@@ -212,8 +213,8 @@ export default function ReverseProxyTargets(props: {
|
|
targets.map((target) =>
|
|
targets.map((target) =>
|
|
target.targetId === targetId
|
|
target.targetId === targetId
|
|
? { ...target, ...data, updated: true }
|
|
? { ...target, ...data, updated: true }
|
|
- : target,
|
|
|
|
- ),
|
|
|
|
|
|
+ : target
|
|
|
|
+ )
|
|
);
|
|
);
|
|
}
|
|
}
|
|
|
|
|
|
@@ -222,7 +223,7 @@ export default function ReverseProxyTargets(props: {
|
|
setLoading(true);
|
|
setLoading(true);
|
|
|
|
|
|
const res = await api.post(`/resource/${params.resourceId}`, {
|
|
const res = await api.post(`/resource/${params.resourceId}`, {
|
|
- ssl: sslEnabled,
|
|
|
|
|
|
+ ssl: sslEnabled
|
|
});
|
|
});
|
|
|
|
|
|
updateResource({ ssl: sslEnabled });
|
|
updateResource({ ssl: sslEnabled });
|
|
@@ -233,7 +234,7 @@ export default function ReverseProxyTargets(props: {
|
|
port: target.port,
|
|
port: target.port,
|
|
// protocol: target.protocol,
|
|
// protocol: target.protocol,
|
|
method: target.method,
|
|
method: target.method,
|
|
- enabled: target.enabled,
|
|
|
|
|
|
+ enabled: target.enabled
|
|
};
|
|
};
|
|
|
|
|
|
if (target.new) {
|
|
if (target.new) {
|
|
@@ -244,7 +245,7 @@ export default function ReverseProxyTargets(props: {
|
|
} else if (target.updated) {
|
|
} else if (target.updated) {
|
|
const res = await api.post(
|
|
const res = await api.post(
|
|
`/target/${target.targetId}`,
|
|
`/target/${target.targetId}`,
|
|
- data,
|
|
|
|
|
|
+ data
|
|
);
|
|
);
|
|
}
|
|
}
|
|
|
|
|
|
@@ -253,23 +254,23 @@ export default function ReverseProxyTargets(props: {
|
|
let res = {
|
|
let res = {
|
|
...t,
|
|
...t,
|
|
new: false,
|
|
new: false,
|
|
- updated: false,
|
|
|
|
|
|
+ updated: false
|
|
};
|
|
};
|
|
return res;
|
|
return res;
|
|
- }),
|
|
|
|
|
|
+ })
|
|
]);
|
|
]);
|
|
}
|
|
}
|
|
|
|
|
|
for (const targetId of targetsToRemove) {
|
|
for (const targetId of targetsToRemove) {
|
|
await api.delete(`/target/${targetId}`);
|
|
await api.delete(`/target/${targetId}`);
|
|
setTargets(
|
|
setTargets(
|
|
- targets.filter((target) => target.targetId !== targetId),
|
|
|
|
|
|
+ targets.filter((target) => target.targetId !== targetId)
|
|
);
|
|
);
|
|
}
|
|
}
|
|
|
|
|
|
toast({
|
|
toast({
|
|
title: "Resource updated",
|
|
title: "Resource updated",
|
|
- description: "Resource and targets updated successfully",
|
|
|
|
|
|
+ description: "Resource and targets updated successfully"
|
|
});
|
|
});
|
|
|
|
|
|
setTargetsToRemove([]);
|
|
setTargetsToRemove([]);
|
|
@@ -280,8 +281,8 @@ export default function ReverseProxyTargets(props: {
|
|
title: "Operation failed",
|
|
title: "Operation failed",
|
|
description: formatAxiosError(
|
|
description: formatAxiosError(
|
|
err,
|
|
err,
|
|
- "An error occurred during the save operation",
|
|
|
|
- ),
|
|
|
|
|
|
+ "An error occurred during the save operation"
|
|
|
|
+ )
|
|
});
|
|
});
|
|
}
|
|
}
|
|
|
|
|
|
@@ -299,13 +300,15 @@ export default function ReverseProxyTargets(props: {
|
|
updateTarget(row.original.targetId, { method: value })
|
|
updateTarget(row.original.targetId, { method: value })
|
|
}
|
|
}
|
|
>
|
|
>
|
|
- <SelectTrigger>{row.original.method}</SelectTrigger>
|
|
|
|
|
|
+ <SelectTrigger className="min-w-[100px]">
|
|
|
|
+ {row.original.method}
|
|
|
|
+ </SelectTrigger>
|
|
<SelectContent>
|
|
<SelectContent>
|
|
<SelectItem value="http">http</SelectItem>
|
|
<SelectItem value="http">http</SelectItem>
|
|
<SelectItem value="https">https</SelectItem>
|
|
<SelectItem value="https">https</SelectItem>
|
|
</SelectContent>
|
|
</SelectContent>
|
|
</Select>
|
|
</Select>
|
|
- ),
|
|
|
|
|
|
+ )
|
|
},
|
|
},
|
|
{
|
|
{
|
|
accessorKey: "ip",
|
|
accessorKey: "ip",
|
|
@@ -313,13 +316,14 @@ export default function ReverseProxyTargets(props: {
|
|
cell: ({ row }) => (
|
|
cell: ({ row }) => (
|
|
<Input
|
|
<Input
|
|
defaultValue={row.original.ip}
|
|
defaultValue={row.original.ip}
|
|
|
|
+ className="min-w-[150px]"
|
|
onBlur={(e) =>
|
|
onBlur={(e) =>
|
|
updateTarget(row.original.targetId, {
|
|
updateTarget(row.original.targetId, {
|
|
- ip: e.target.value,
|
|
|
|
|
|
+ ip: e.target.value
|
|
})
|
|
})
|
|
}
|
|
}
|
|
/>
|
|
/>
|
|
- ),
|
|
|
|
|
|
+ )
|
|
},
|
|
},
|
|
{
|
|
{
|
|
accessorKey: "port",
|
|
accessorKey: "port",
|
|
@@ -328,13 +332,14 @@ export default function ReverseProxyTargets(props: {
|
|
<Input
|
|
<Input
|
|
type="number"
|
|
type="number"
|
|
defaultValue={row.original.port}
|
|
defaultValue={row.original.port}
|
|
|
|
+ className="min-w-[100px]"
|
|
onBlur={(e) =>
|
|
onBlur={(e) =>
|
|
updateTarget(row.original.targetId, {
|
|
updateTarget(row.original.targetId, {
|
|
- port: parseInt(e.target.value, 10),
|
|
|
|
|
|
+ port: parseInt(e.target.value, 10)
|
|
})
|
|
})
|
|
}
|
|
}
|
|
/>
|
|
/>
|
|
- ),
|
|
|
|
|
|
+ )
|
|
},
|
|
},
|
|
// {
|
|
// {
|
|
// accessorKey: "protocol",
|
|
// accessorKey: "protocol",
|
|
@@ -364,7 +369,7 @@ export default function ReverseProxyTargets(props: {
|
|
updateTarget(row.original.targetId, { enabled: val })
|
|
updateTarget(row.original.targetId, { enabled: val })
|
|
}
|
|
}
|
|
/>
|
|
/>
|
|
- ),
|
|
|
|
|
|
+ )
|
|
},
|
|
},
|
|
{
|
|
{
|
|
id: "actions",
|
|
id: "actions",
|
|
@@ -387,8 +392,8 @@ export default function ReverseProxyTargets(props: {
|
|
</Button>
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
</>
|
|
</>
|
|
- ),
|
|
|
|
- },
|
|
|
|
|
|
+ )
|
|
|
|
+ }
|
|
];
|
|
];
|
|
|
|
|
|
const table = useReactTable({
|
|
const table = useReactTable({
|
|
@@ -397,7 +402,7 @@ export default function ReverseProxyTargets(props: {
|
|
getCoreRowModel: getCoreRowModel(),
|
|
getCoreRowModel: getCoreRowModel(),
|
|
getPaginationRowModel: getPaginationRowModel(),
|
|
getPaginationRowModel: getPaginationRowModel(),
|
|
getSortedRowModel: getSortedRowModel(),
|
|
getSortedRowModel: getSortedRowModel(),
|
|
- getFilteredRowModel: getFilteredRowModel(),
|
|
|
|
|
|
+ getFilteredRowModel: getFilteredRowModel()
|
|
});
|
|
});
|
|
|
|
|
|
if (pageLoading) {
|
|
if (pageLoading) {
|
|
@@ -437,7 +442,7 @@ export default function ReverseProxyTargets(props: {
|
|
<Form {...addTargetForm}>
|
|
<Form {...addTargetForm}>
|
|
<form
|
|
<form
|
|
onSubmit={addTargetForm.handleSubmit(
|
|
onSubmit={addTargetForm.handleSubmit(
|
|
- addTarget as any,
|
|
|
|
|
|
+ addTarget as any
|
|
)}
|
|
)}
|
|
className="space-y-4"
|
|
className="space-y-4"
|
|
>
|
|
>
|
|
@@ -452,11 +457,11 @@ export default function ReverseProxyTargets(props: {
|
|
<Select
|
|
<Select
|
|
{...field}
|
|
{...field}
|
|
onValueChange={(
|
|
onValueChange={(
|
|
- value,
|
|
|
|
|
|
+ value
|
|
) => {
|
|
) => {
|
|
addTargetForm.setValue(
|
|
addTargetForm.setValue(
|
|
"method",
|
|
"method",
|
|
- value,
|
|
|
|
|
|
+ value
|
|
);
|
|
);
|
|
}}
|
|
}}
|
|
>
|
|
>
|
|
@@ -585,10 +590,10 @@ export default function ReverseProxyTargets(props: {
|
|
.column
|
|
.column
|
|
.columnDef
|
|
.columnDef
|
|
.header,
|
|
.header,
|
|
- header.getContext(),
|
|
|
|
|
|
+ header.getContext()
|
|
)}
|
|
)}
|
|
</TableHead>
|
|
</TableHead>
|
|
- ),
|
|
|
|
|
|
+ )
|
|
)}
|
|
)}
|
|
</TableRow>
|
|
</TableRow>
|
|
))}
|
|
))}
|
|
@@ -607,7 +612,7 @@ export default function ReverseProxyTargets(props: {
|
|
cell.column
|
|
cell.column
|
|
.columnDef
|
|
.columnDef
|
|
.cell,
|
|
.cell,
|
|
- cell.getContext(),
|
|
|
|
|
|
+ cell.getContext()
|
|
)}
|
|
)}
|
|
</TableCell>
|
|
</TableCell>
|
|
))}
|
|
))}
|
|
@@ -644,36 +649,36 @@ export default function ReverseProxyTargets(props: {
|
|
|
|
|
|
function isIPInSubnet(subnet: string, ip: string): boolean {
|
|
function isIPInSubnet(subnet: string, ip: string): boolean {
|
|
// Split subnet into IP and mask parts
|
|
// Split subnet into IP and mask parts
|
|
- const [subnetIP, maskBits] = subnet.split('/');
|
|
|
|
|
|
+ const [subnetIP, maskBits] = subnet.split("/");
|
|
const mask = parseInt(maskBits);
|
|
const mask = parseInt(maskBits);
|
|
-
|
|
|
|
|
|
+
|
|
if (mask < 0 || mask > 32) {
|
|
if (mask < 0 || mask > 32) {
|
|
- throw new Error('Invalid subnet mask. Must be between 0 and 32.');
|
|
|
|
|
|
+ throw new Error("Invalid subnet mask. Must be between 0 and 32.");
|
|
}
|
|
}
|
|
|
|
|
|
// Convert IP addresses to binary numbers
|
|
// Convert IP addresses to binary numbers
|
|
const subnetNum = ipToNumber(subnetIP);
|
|
const subnetNum = ipToNumber(subnetIP);
|
|
const ipNum = ipToNumber(ip);
|
|
const ipNum = ipToNumber(ip);
|
|
-
|
|
|
|
|
|
+
|
|
// Calculate subnet mask
|
|
// Calculate subnet mask
|
|
const maskNum = mask === 32 ? -1 : ~((1 << (32 - mask)) - 1);
|
|
const maskNum = mask === 32 ? -1 : ~((1 << (32 - mask)) - 1);
|
|
-
|
|
|
|
|
|
+
|
|
// Check if the IP is in the subnet
|
|
// Check if the IP is in the subnet
|
|
return (subnetNum & maskNum) === (ipNum & maskNum);
|
|
return (subnetNum & maskNum) === (ipNum & maskNum);
|
|
}
|
|
}
|
|
|
|
|
|
function ipToNumber(ip: string): number {
|
|
function ipToNumber(ip: string): number {
|
|
// Validate IP address format
|
|
// Validate IP address format
|
|
- const parts = ip.split('.');
|
|
|
|
|
|
+ const parts = ip.split(".");
|
|
if (parts.length !== 4) {
|
|
if (parts.length !== 4) {
|
|
- throw new Error('Invalid IP address format');
|
|
|
|
|
|
+ throw new Error("Invalid IP address format");
|
|
}
|
|
}
|
|
-
|
|
|
|
|
|
+
|
|
// Convert IP octets to 32-bit number
|
|
// Convert IP octets to 32-bit number
|
|
return parts.reduce((num, octet) => {
|
|
return parts.reduce((num, octet) => {
|
|
const oct = parseInt(octet);
|
|
const oct = parseInt(octet);
|
|
if (isNaN(oct) || oct < 0 || oct > 255) {
|
|
if (isNaN(oct) || oct < 0 || oct > 255) {
|
|
- throw new Error('Invalid IP address octet');
|
|
|
|
|
|
+ throw new Error("Invalid IP address octet");
|
|
}
|
|
}
|
|
return (num << 8) + oct;
|
|
return (num << 8) + oct;
|
|
}, 0);
|
|
}, 0);
|