mirror of
https://github.com/fosrl/pangolin.git
synced 2026-03-03 17:26:38 +00:00
clean up targets input a little
This commit is contained in:
@@ -181,7 +181,7 @@
|
|||||||
"baseDomain": "Base Domain",
|
"baseDomain": "Base Domain",
|
||||||
"subdomnainDescription": "The subdomain where the resource will be accessible.",
|
"subdomnainDescription": "The subdomain where the resource will be accessible.",
|
||||||
"resourceRawSettings": "TCP/UDP Settings",
|
"resourceRawSettings": "TCP/UDP Settings",
|
||||||
"resourceRawSettingsDescription": "Configure how the resource will be accessed over TCP/UDP. You map the resource to a port on the host Pangolin server, so you can access the resource from server-public-ip:mapped-port.",
|
"resourceRawSettingsDescription": "Configure how the resource will be accessed over TCP/UDP",
|
||||||
"protocol": "Protocol",
|
"protocol": "Protocol",
|
||||||
"protocolSelect": "Select a protocol",
|
"protocolSelect": "Select a protocol",
|
||||||
"resourcePortNumber": "Port Number",
|
"resourcePortNumber": "Port Number",
|
||||||
@@ -499,7 +499,7 @@
|
|||||||
"proxyUpdatedDescription": "Proxy settings have been updated successfully",
|
"proxyUpdatedDescription": "Proxy settings have been updated successfully",
|
||||||
"proxyErrorUpdate": "Failed to update proxy settings",
|
"proxyErrorUpdate": "Failed to update proxy settings",
|
||||||
"proxyErrorUpdateDescription": "An error occurred while updating proxy settings",
|
"proxyErrorUpdateDescription": "An error occurred while updating proxy settings",
|
||||||
"targetAddr": "IP / Hostname",
|
"targetAddr": "Host",
|
||||||
"targetPort": "Port",
|
"targetPort": "Port",
|
||||||
"targetProtocol": "Protocol",
|
"targetProtocol": "Protocol",
|
||||||
"targetTlsSettings": "Secure Connection Configuration",
|
"targetTlsSettings": "Secure Connection Configuration",
|
||||||
|
|||||||
@@ -123,10 +123,9 @@ const addTargetSchema = z
|
|||||||
ip: z.string().refine(isTargetValid),
|
ip: z.string().refine(isTargetValid),
|
||||||
method: z.string().nullable(),
|
method: z.string().nullable(),
|
||||||
port: z.coerce.number<number>().int().positive(),
|
port: z.coerce.number<number>().int().positive(),
|
||||||
siteId: z.int()
|
siteId: z.int().positive({
|
||||||
.positive({
|
error: "You must select a site for a target."
|
||||||
error: "You must select a site for a target."
|
}),
|
||||||
}),
|
|
||||||
path: z.string().optional().nullable(),
|
path: z.string().optional().nullable(),
|
||||||
pathMatchType: z
|
pathMatchType: z
|
||||||
.enum(["exact", "prefix", "regex"])
|
.enum(["exact", "prefix", "regex"])
|
||||||
@@ -546,11 +545,11 @@ export default function ReverseProxyTargets(props: {
|
|||||||
prev.map((t) =>
|
prev.map((t) =>
|
||||||
t.targetId === target.targetId
|
t.targetId === target.targetId
|
||||||
? {
|
? {
|
||||||
...t,
|
...t,
|
||||||
targetId: response.data.data.targetId,
|
targetId: response.data.data.targetId,
|
||||||
new: false,
|
new: false,
|
||||||
updated: false
|
updated: false
|
||||||
}
|
}
|
||||||
: t
|
: t
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
@@ -607,16 +606,16 @@ export default function ReverseProxyTargets(props: {
|
|||||||
|
|
||||||
const newTarget: LocalTarget = {
|
const newTarget: LocalTarget = {
|
||||||
...data,
|
...data,
|
||||||
path: isHttp ? (data.path || null) : null,
|
path: isHttp ? data.path || null : null,
|
||||||
pathMatchType: isHttp ? (data.pathMatchType || null) : null,
|
pathMatchType: isHttp ? data.pathMatchType || null : null,
|
||||||
rewritePath: isHttp ? (data.rewritePath || null) : null,
|
rewritePath: isHttp ? data.rewritePath || null : null,
|
||||||
rewritePathType: isHttp ? (data.rewritePathType || null) : null,
|
rewritePathType: isHttp ? data.rewritePathType || null : null,
|
||||||
siteType: site?.type || null,
|
siteType: site?.type || null,
|
||||||
enabled: true,
|
enabled: true,
|
||||||
targetId: new Date().getTime(),
|
targetId: new Date().getTime(),
|
||||||
new: true,
|
new: true,
|
||||||
resourceId: resource.resourceId,
|
resourceId: resource.resourceId,
|
||||||
priority: isHttp ? (data.priority || 100) : 100,
|
priority: isHttp ? data.priority || 100 : 100,
|
||||||
hcEnabled: false,
|
hcEnabled: false,
|
||||||
hcPath: null,
|
hcPath: null,
|
||||||
hcMethod: null,
|
hcMethod: null,
|
||||||
@@ -631,7 +630,7 @@ export default function ReverseProxyTargets(props: {
|
|||||||
hcStatus: null,
|
hcStatus: null,
|
||||||
hcMode: null,
|
hcMode: null,
|
||||||
hcUnhealthyInterval: null,
|
hcUnhealthyInterval: null,
|
||||||
hcTlsServerName: null,
|
hcTlsServerName: null
|
||||||
};
|
};
|
||||||
|
|
||||||
setTargets([...targets, newTarget]);
|
setTargets([...targets, newTarget]);
|
||||||
@@ -653,11 +652,11 @@ export default function ReverseProxyTargets(props: {
|
|||||||
targets.map((target) =>
|
targets.map((target) =>
|
||||||
target.targetId === targetId
|
target.targetId === targetId
|
||||||
? {
|
? {
|
||||||
...target,
|
...target,
|
||||||
...data,
|
...data,
|
||||||
updated: true,
|
updated: true,
|
||||||
siteType: site ? site.type : target.siteType
|
siteType: site ? site.type : target.siteType
|
||||||
}
|
}
|
||||||
: target
|
: target
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
@@ -668,10 +667,10 @@ export default function ReverseProxyTargets(props: {
|
|||||||
targets.map((target) =>
|
targets.map((target) =>
|
||||||
target.targetId === targetId
|
target.targetId === targetId
|
||||||
? {
|
? {
|
||||||
...target,
|
...target,
|
||||||
...config,
|
...config,
|
||||||
updated: true
|
updated: true
|
||||||
}
|
}
|
||||||
: target
|
: target
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
@@ -733,7 +732,7 @@ export default function ReverseProxyTargets(props: {
|
|||||||
hcStatus: target.hcStatus || null,
|
hcStatus: target.hcStatus || null,
|
||||||
hcUnhealthyInterval: target.hcUnhealthyInterval || null,
|
hcUnhealthyInterval: target.hcUnhealthyInterval || null,
|
||||||
hcMode: target.hcMode || null,
|
hcMode: target.hcMode || null,
|
||||||
hcTlsServerName: target.hcTlsServerName,
|
hcTlsServerName: target.hcTlsServerName
|
||||||
};
|
};
|
||||||
|
|
||||||
// Only include path-related fields for HTTP resources
|
// Only include path-related fields for HTTP resources
|
||||||
@@ -833,7 +832,7 @@ export default function ReverseProxyTargets(props: {
|
|||||||
const priorityColumn: ColumnDef<LocalTarget> = {
|
const priorityColumn: ColumnDef<LocalTarget> = {
|
||||||
id: "priority",
|
id: "priority",
|
||||||
header: () => (
|
header: () => (
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2 p-3">
|
||||||
{t("priority")}
|
{t("priority")}
|
||||||
<TooltipProvider>
|
<TooltipProvider>
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
@@ -877,7 +876,7 @@ export default function ReverseProxyTargets(props: {
|
|||||||
|
|
||||||
const healthCheckColumn: ColumnDef<LocalTarget> = {
|
const healthCheckColumn: ColumnDef<LocalTarget> = {
|
||||||
accessorKey: "healthCheck",
|
accessorKey: "healthCheck",
|
||||||
header: () => (<span className="p-3">{t("healthCheck")}</span>),
|
header: () => <span className="p-3">{t("healthCheck")}</span>,
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
const status = row.original.hcHealth || "unknown";
|
const status = row.original.hcHealth || "unknown";
|
||||||
const isEnabled = row.original.hcEnabled;
|
const isEnabled = row.original.hcEnabled;
|
||||||
@@ -923,18 +922,16 @@ export default function ReverseProxyTargets(props: {
|
|||||||
{row.original.siteType === "newt" ? (
|
{row.original.siteType === "newt" ? (
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
className="flex items-center justify-between gap-2 p-2 w-full text-left cursor-pointer"
|
className="flex items-center gap-2 w-full text-left cursor-pointer"
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
openHealthCheckDialog(row.original)
|
openHealthCheckDialog(row.original)
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Badge variant={getStatusColor(status)}>
|
|
||||||
<div className="flex items-center gap-1">
|
|
||||||
{getStatusIcon(status)}
|
|
||||||
{getStatusText(status)}
|
|
||||||
</div>
|
|
||||||
</Badge>
|
|
||||||
<Settings className="h-4 w-4" />
|
<Settings className="h-4 w-4" />
|
||||||
|
<div className="flex items-center gap-1">
|
||||||
|
{getStatusIcon(status)}
|
||||||
|
{getStatusText(status)}
|
||||||
|
</div>
|
||||||
</Button>
|
</Button>
|
||||||
) : (
|
) : (
|
||||||
<span>-</span>
|
<span>-</span>
|
||||||
@@ -949,7 +946,7 @@ export default function ReverseProxyTargets(props: {
|
|||||||
|
|
||||||
const matchPathColumn: ColumnDef<LocalTarget> = {
|
const matchPathColumn: ColumnDef<LocalTarget> = {
|
||||||
accessorKey: "path",
|
accessorKey: "path",
|
||||||
header: () => (<span className="p-3">{t("matchPath")}</span>),
|
header: () => <span className="p-3">{t("matchPath")}</span>,
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
const hasPathMatch = !!(
|
const hasPathMatch = !!(
|
||||||
row.original.path || row.original.pathMatchType
|
row.original.path || row.original.pathMatchType
|
||||||
@@ -1011,7 +1008,7 @@ export default function ReverseProxyTargets(props: {
|
|||||||
|
|
||||||
const addressColumn: ColumnDef<LocalTarget> = {
|
const addressColumn: ColumnDef<LocalTarget> = {
|
||||||
accessorKey: "address",
|
accessorKey: "address",
|
||||||
header: () => (<span className="p-3">{t("address")}</span>),
|
header: () => <span className="p-3">{t("address")}</span>,
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
const selectedSite = sites.find(
|
const selectedSite = sites.find(
|
||||||
(site) => site.siteId === row.original.siteId
|
(site) => site.siteId === row.original.siteId
|
||||||
@@ -1064,7 +1061,7 @@ export default function ReverseProxyTargets(props: {
|
|||||||
className={cn(
|
className={cn(
|
||||||
"w-[180px] justify-between text-sm border-r pr-4 rounded-none h-8 hover:bg-transparent",
|
"w-[180px] justify-between text-sm border-r pr-4 rounded-none h-8 hover:bg-transparent",
|
||||||
!row.original.siteId &&
|
!row.original.siteId &&
|
||||||
"text-muted-foreground"
|
"text-muted-foreground"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<span className="truncate max-w-[150px]">
|
<span className="truncate max-w-[150px]">
|
||||||
@@ -1132,8 +1129,12 @@ export default function ReverseProxyTargets(props: {
|
|||||||
{row.original.method || "http"}
|
{row.original.method || "http"}
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
<SelectItem value="http">http</SelectItem>
|
<SelectItem value="http">
|
||||||
<SelectItem value="https">https</SelectItem>
|
http
|
||||||
|
</SelectItem>
|
||||||
|
<SelectItem value="https">
|
||||||
|
https
|
||||||
|
</SelectItem>
|
||||||
<SelectItem value="h2c">h2c</SelectItem>
|
<SelectItem value="h2c">h2c</SelectItem>
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
@@ -1147,7 +1148,7 @@ export default function ReverseProxyTargets(props: {
|
|||||||
|
|
||||||
<Input
|
<Input
|
||||||
defaultValue={row.original.ip}
|
defaultValue={row.original.ip}
|
||||||
placeholder="IP / Hostname"
|
placeholder="Host"
|
||||||
className="flex-1 min-w-[120px] pl-0 border-none placeholder-gray-400"
|
className="flex-1 min-w-[120px] pl-0 border-none placeholder-gray-400"
|
||||||
onBlur={(e) => {
|
onBlur={(e) => {
|
||||||
const input = e.target.value.trim();
|
const input = e.target.value.trim();
|
||||||
@@ -1225,7 +1226,7 @@ export default function ReverseProxyTargets(props: {
|
|||||||
|
|
||||||
const rewritePathColumn: ColumnDef<LocalTarget> = {
|
const rewritePathColumn: ColumnDef<LocalTarget> = {
|
||||||
accessorKey: "rewritePath",
|
accessorKey: "rewritePath",
|
||||||
header: () => (<span className="p-3">{t("rewritePath")}</span>),
|
header: () => <span className="p-3">{t("rewritePath")}</span>,
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
const hasRewritePath = !!(
|
const hasRewritePath = !!(
|
||||||
row.original.rewritePath || row.original.rewritePathType
|
row.original.rewritePath || row.original.rewritePathType
|
||||||
@@ -1295,7 +1296,7 @@ export default function ReverseProxyTargets(props: {
|
|||||||
|
|
||||||
const enabledColumn: ColumnDef<LocalTarget> = {
|
const enabledColumn: ColumnDef<LocalTarget> = {
|
||||||
accessorKey: "enabled",
|
accessorKey: "enabled",
|
||||||
header: () => (<span className="p-3">{t("enabled")}</span>),
|
header: () => <span className="p-3">{t("enabled")}</span>,
|
||||||
cell: ({ row }) => (
|
cell: ({ row }) => (
|
||||||
<div className="flex items-center justify-center w-full">
|
<div className="flex items-center justify-center w-full">
|
||||||
<Switch
|
<Switch
|
||||||
@@ -1316,7 +1317,7 @@ export default function ReverseProxyTargets(props: {
|
|||||||
|
|
||||||
const actionsColumn: ColumnDef<LocalTarget> = {
|
const actionsColumn: ColumnDef<LocalTarget> = {
|
||||||
id: "actions",
|
id: "actions",
|
||||||
header: () => (<span className="p-3">{t("actions")}</span>),
|
header: () => <span className="p-3">{t("actions")}</span>,
|
||||||
cell: ({ row }) => (
|
cell: ({ row }) => (
|
||||||
<div className="flex items-center w-full">
|
<div className="flex items-center w-full">
|
||||||
<Button
|
<Button
|
||||||
@@ -1399,21 +1400,30 @@ export default function ReverseProxyTargets(props: {
|
|||||||
<TableRow key={headerGroup.id}>
|
<TableRow key={headerGroup.id}>
|
||||||
{headerGroup.headers.map(
|
{headerGroup.headers.map(
|
||||||
(header) => {
|
(header) => {
|
||||||
const isActionsColumn = header.column.id === "actions";
|
const isActionsColumn =
|
||||||
|
header.column
|
||||||
|
.id ===
|
||||||
|
"actions";
|
||||||
return (
|
return (
|
||||||
<TableHead
|
<TableHead
|
||||||
key={header.id}
|
key={
|
||||||
className={isActionsColumn ? "sticky right-0 z-10 w-auto min-w-fit bg-card" : ""}
|
header.id
|
||||||
|
}
|
||||||
|
className={
|
||||||
|
isActionsColumn
|
||||||
|
? "sticky right-0 z-10 w-auto min-w-fit bg-card"
|
||||||
|
: ""
|
||||||
|
}
|
||||||
>
|
>
|
||||||
{header.isPlaceholder
|
{header.isPlaceholder
|
||||||
? null
|
? null
|
||||||
: flexRender(
|
: flexRender(
|
||||||
header
|
header
|
||||||
.column
|
.column
|
||||||
.columnDef
|
.columnDef
|
||||||
.header,
|
.header,
|
||||||
header.getContext()
|
header.getContext()
|
||||||
)}
|
)}
|
||||||
</TableHead>
|
</TableHead>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1430,13 +1440,20 @@ export default function ReverseProxyTargets(props: {
|
|||||||
{row
|
{row
|
||||||
.getVisibleCells()
|
.getVisibleCells()
|
||||||
.map((cell) => {
|
.map((cell) => {
|
||||||
const isActionsColumn = cell.column.id === "actions";
|
const isActionsColumn =
|
||||||
|
cell.column
|
||||||
|
.id ===
|
||||||
|
"actions";
|
||||||
return (
|
return (
|
||||||
<TableCell
|
<TableCell
|
||||||
key={
|
key={
|
||||||
cell.id
|
cell.id
|
||||||
}
|
}
|
||||||
className={isActionsColumn ? "sticky right-0 z-10 w-auto min-w-fit bg-card" : ""}
|
className={
|
||||||
|
isActionsColumn
|
||||||
|
? "sticky right-0 z-10 w-auto min-w-fit bg-card"
|
||||||
|
: ""
|
||||||
|
}
|
||||||
>
|
>
|
||||||
{flexRender(
|
{flexRender(
|
||||||
cell
|
cell
|
||||||
@@ -1492,7 +1509,7 @@ export default function ReverseProxyTargets(props: {
|
|||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<div className="text-center py-8 border-2 border-dashed border-muted rounded-lg p-4">
|
<div className="text-center p-4">
|
||||||
<p className="text-muted-foreground mb-4">
|
<p className="text-muted-foreground mb-4">
|
||||||
{t("targetNoOne")}
|
{t("targetNoOne")}
|
||||||
</p>
|
</p>
|
||||||
@@ -1721,7 +1738,9 @@ export default function ReverseProxyTargets(props: {
|
|||||||
defaultChecked={
|
defaultChecked={
|
||||||
field.value || false
|
field.value || false
|
||||||
}
|
}
|
||||||
onCheckedChange={(val) => {
|
onCheckedChange={(
|
||||||
|
val
|
||||||
|
) => {
|
||||||
field.onChange(val);
|
field.onChange(val);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@@ -1730,19 +1749,37 @@ export default function ReverseProxyTargets(props: {
|
|||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{proxySettingsForm.watch("proxyProtocol") && (
|
{proxySettingsForm.watch(
|
||||||
|
"proxyProtocol"
|
||||||
|
) && (
|
||||||
<>
|
<>
|
||||||
<FormField
|
<FormField
|
||||||
control={proxySettingsForm.control}
|
control={
|
||||||
|
proxySettingsForm.control
|
||||||
|
}
|
||||||
name="proxyProtocolVersion"
|
name="proxyProtocolVersion"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>{t("proxyProtocolVersion")}</FormLabel>
|
<FormLabel>
|
||||||
|
{t(
|
||||||
|
"proxyProtocolVersion"
|
||||||
|
)}
|
||||||
|
</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Select
|
<Select
|
||||||
value={String(field.value || 1)}
|
value={String(
|
||||||
onValueChange={(value) =>
|
field.value ||
|
||||||
field.onChange(parseInt(value, 10))
|
1
|
||||||
|
)}
|
||||||
|
onValueChange={(
|
||||||
|
value
|
||||||
|
) =>
|
||||||
|
field.onChange(
|
||||||
|
parseInt(
|
||||||
|
value,
|
||||||
|
10
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<SelectTrigger>
|
<SelectTrigger>
|
||||||
@@ -1750,16 +1787,22 @@ export default function ReverseProxyTargets(props: {
|
|||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
<SelectItem value="1">
|
<SelectItem value="1">
|
||||||
{t("version1")}
|
{t(
|
||||||
|
"version1"
|
||||||
|
)}
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
<SelectItem value="2">
|
<SelectItem value="2">
|
||||||
{t("version2")}
|
{t(
|
||||||
|
"version2"
|
||||||
|
)}
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormDescription>
|
<FormDescription>
|
||||||
{t("versionDescription")}
|
{t(
|
||||||
|
"versionDescription"
|
||||||
|
)}
|
||||||
</FormDescription>
|
</FormDescription>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
@@ -1768,7 +1811,10 @@ export default function ReverseProxyTargets(props: {
|
|||||||
<Alert>
|
<Alert>
|
||||||
<AlertTriangle className="h-4 w-4" />
|
<AlertTriangle className="h-4 w-4" />
|
||||||
<AlertDescription>
|
<AlertDescription>
|
||||||
<strong>{t("warning")}:</strong> {t("proxyProtocolWarning")}
|
<strong>
|
||||||
|
{t("warning")}:
|
||||||
|
</strong>{" "}
|
||||||
|
{t("proxyProtocolWarning")}
|
||||||
</AlertDescription>
|
</AlertDescription>
|
||||||
</Alert>
|
</Alert>
|
||||||
</>
|
</>
|
||||||
@@ -1835,8 +1881,9 @@ export default function ReverseProxyTargets(props: {
|
|||||||
hcUnhealthyInterval:
|
hcUnhealthyInterval:
|
||||||
selectedTargetForHealthCheck.hcUnhealthyInterval ||
|
selectedTargetForHealthCheck.hcUnhealthyInterval ||
|
||||||
30,
|
30,
|
||||||
hcTlsServerName: selectedTargetForHealthCheck.hcTlsServerName ||
|
hcTlsServerName:
|
||||||
undefined,
|
selectedTargetForHealthCheck.hcTlsServerName ||
|
||||||
|
undefined
|
||||||
}}
|
}}
|
||||||
onChanges={async (config) => {
|
onChanges={async (config) => {
|
||||||
if (selectedTargetForHealthCheck) {
|
if (selectedTargetForHealthCheck) {
|
||||||
|
|||||||
@@ -432,16 +432,16 @@ export default function Page() {
|
|||||||
|
|
||||||
const newTarget: LocalTarget = {
|
const newTarget: LocalTarget = {
|
||||||
...data,
|
...data,
|
||||||
path: isHttp ? (data.path || null) : null,
|
path: isHttp ? data.path || null : null,
|
||||||
pathMatchType: isHttp ? (data.pathMatchType || null) : null,
|
pathMatchType: isHttp ? data.pathMatchType || null : null,
|
||||||
rewritePath: isHttp ? (data.rewritePath || null) : null,
|
rewritePath: isHttp ? data.rewritePath || null : null,
|
||||||
rewritePathType: isHttp ? (data.rewritePathType || null) : null,
|
rewritePathType: isHttp ? data.rewritePathType || null : null,
|
||||||
siteType: site?.type || null,
|
siteType: site?.type || null,
|
||||||
enabled: true,
|
enabled: true,
|
||||||
targetId: new Date().getTime(),
|
targetId: new Date().getTime(),
|
||||||
new: true,
|
new: true,
|
||||||
resourceId: 0, // Will be set when resource is created
|
resourceId: 0, // Will be set when resource is created
|
||||||
priority: isHttp ? (data.priority || 100) : 100, // Default priority
|
priority: isHttp ? data.priority || 100 : 100, // Default priority
|
||||||
hcEnabled: false,
|
hcEnabled: false,
|
||||||
hcPath: null,
|
hcPath: null,
|
||||||
hcMethod: null,
|
hcMethod: null,
|
||||||
@@ -507,7 +507,7 @@ export default function Page() {
|
|||||||
try {
|
try {
|
||||||
const payload = {
|
const payload = {
|
||||||
name: baseData.name,
|
name: baseData.name,
|
||||||
http: baseData.http,
|
http: baseData.http
|
||||||
};
|
};
|
||||||
|
|
||||||
let sanitizedSubdomain: string | undefined;
|
let sanitizedSubdomain: string | undefined;
|
||||||
@@ -577,7 +577,8 @@ export default function Page() {
|
|||||||
hcFollowRedirects:
|
hcFollowRedirects:
|
||||||
target.hcFollowRedirects || null,
|
target.hcFollowRedirects || null,
|
||||||
hcStatus: target.hcStatus || null,
|
hcStatus: target.hcStatus || null,
|
||||||
hcUnhealthyInterval: target.hcUnhealthyInterval || null,
|
hcUnhealthyInterval:
|
||||||
|
target.hcUnhealthyInterval || null,
|
||||||
hcMode: target.hcMode || null,
|
hcMode: target.hcMode || null,
|
||||||
hcTlsServerName: target.hcTlsServerName
|
hcTlsServerName: target.hcTlsServerName
|
||||||
};
|
};
|
||||||
@@ -737,7 +738,7 @@ export default function Page() {
|
|||||||
const priorityColumn: ColumnDef<LocalTarget> = {
|
const priorityColumn: ColumnDef<LocalTarget> = {
|
||||||
id: "priority",
|
id: "priority",
|
||||||
header: () => (
|
header: () => (
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2 p-3">
|
||||||
{t("priority")}
|
{t("priority")}
|
||||||
<TooltipProvider>
|
<TooltipProvider>
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
@@ -780,7 +781,7 @@ export default function Page() {
|
|||||||
|
|
||||||
const healthCheckColumn: ColumnDef<LocalTarget> = {
|
const healthCheckColumn: ColumnDef<LocalTarget> = {
|
||||||
accessorKey: "healthCheck",
|
accessorKey: "healthCheck",
|
||||||
header: () => (<span className="p-3">{t("healthCheck")}</span>),
|
header: () => <span className="p-3">{t("healthCheck")}</span>,
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
const status = row.original.hcHealth || "unknown";
|
const status = row.original.hcHealth || "unknown";
|
||||||
const isEnabled = row.original.hcEnabled;
|
const isEnabled = row.original.hcEnabled;
|
||||||
@@ -826,18 +827,16 @@ export default function Page() {
|
|||||||
{row.original.siteType === "newt" ? (
|
{row.original.siteType === "newt" ? (
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
className="flex items-center justify-between gap-2 p-2 w-full text-left cursor-pointer"
|
className="flex items-center gap-2 w-full text-left cursor-pointer"
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
openHealthCheckDialog(row.original)
|
openHealthCheckDialog(row.original)
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Badge variant={getStatusColor(status)}>
|
|
||||||
<div className="flex items-center gap-1">
|
|
||||||
{getStatusIcon(status)}
|
|
||||||
{getStatusText(status)}
|
|
||||||
</div>
|
|
||||||
</Badge>
|
|
||||||
<Settings className="h-4 w-4" />
|
<Settings className="h-4 w-4" />
|
||||||
|
<div className="flex items-center gap-1">
|
||||||
|
{getStatusIcon(status)}
|
||||||
|
{getStatusText(status)}
|
||||||
|
</div>
|
||||||
</Button>
|
</Button>
|
||||||
) : (
|
) : (
|
||||||
<span>-</span>
|
<span>-</span>
|
||||||
@@ -852,7 +851,7 @@ export default function Page() {
|
|||||||
|
|
||||||
const matchPathColumn: ColumnDef<LocalTarget> = {
|
const matchPathColumn: ColumnDef<LocalTarget> = {
|
||||||
accessorKey: "path",
|
accessorKey: "path",
|
||||||
header: () => (<span className="p-3">{t("matchPath")}</span>),
|
header: () => <span className="p-3">{t("matchPath")}</span>,
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
const hasPathMatch = !!(
|
const hasPathMatch = !!(
|
||||||
row.original.path || row.original.pathMatchType
|
row.original.path || row.original.pathMatchType
|
||||||
@@ -914,7 +913,7 @@ export default function Page() {
|
|||||||
|
|
||||||
const addressColumn: ColumnDef<LocalTarget> = {
|
const addressColumn: ColumnDef<LocalTarget> = {
|
||||||
accessorKey: "address",
|
accessorKey: "address",
|
||||||
header: () => (<span className="p-3">{t("address")}</span>),
|
header: () => <span className="p-3">{t("address")}</span>,
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
const selectedSite = sites.find(
|
const selectedSite = sites.find(
|
||||||
(site) => site.siteId === row.original.siteId
|
(site) => site.siteId === row.original.siteId
|
||||||
@@ -1035,8 +1034,12 @@ export default function Page() {
|
|||||||
{row.original.method || "http"}
|
{row.original.method || "http"}
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
<SelectItem value="http">http</SelectItem>
|
<SelectItem value="http">
|
||||||
<SelectItem value="https">https</SelectItem>
|
http
|
||||||
|
</SelectItem>
|
||||||
|
<SelectItem value="https">
|
||||||
|
https
|
||||||
|
</SelectItem>
|
||||||
<SelectItem value="h2c">h2c</SelectItem>
|
<SelectItem value="h2c">h2c</SelectItem>
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
@@ -1050,7 +1053,7 @@ export default function Page() {
|
|||||||
|
|
||||||
<Input
|
<Input
|
||||||
defaultValue={row.original.ip}
|
defaultValue={row.original.ip}
|
||||||
placeholder="IP / Hostname"
|
placeholder="Host"
|
||||||
className="flex-1 min-w-[120px] pl-0 border-none placeholder-gray-400"
|
className="flex-1 min-w-[120px] pl-0 border-none placeholder-gray-400"
|
||||||
onBlur={(e) => {
|
onBlur={(e) => {
|
||||||
const input = e.target.value.trim();
|
const input = e.target.value.trim();
|
||||||
@@ -1128,7 +1131,7 @@ export default function Page() {
|
|||||||
|
|
||||||
const rewritePathColumn: ColumnDef<LocalTarget> = {
|
const rewritePathColumn: ColumnDef<LocalTarget> = {
|
||||||
accessorKey: "rewritePath",
|
accessorKey: "rewritePath",
|
||||||
header: () => (<span className="p-3">{t("rewritePath")}</span>),
|
header: () => <span className="p-3">{t("rewritePath")}</span>,
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
const hasRewritePath = !!(
|
const hasRewritePath = !!(
|
||||||
row.original.rewritePath || row.original.rewritePathType
|
row.original.rewritePath || row.original.rewritePathType
|
||||||
@@ -1198,7 +1201,7 @@ export default function Page() {
|
|||||||
|
|
||||||
const enabledColumn: ColumnDef<LocalTarget> = {
|
const enabledColumn: ColumnDef<LocalTarget> = {
|
||||||
accessorKey: "enabled",
|
accessorKey: "enabled",
|
||||||
header: () => (<span className="p-3">{t("enabled")}</span>),
|
header: () => <span className="p-3">{t("enabled")}</span>,
|
||||||
cell: ({ row }) => (
|
cell: ({ row }) => (
|
||||||
<div className="flex items-center justify-center w-full">
|
<div className="flex items-center justify-center w-full">
|
||||||
<Switch
|
<Switch
|
||||||
@@ -1219,7 +1222,7 @@ export default function Page() {
|
|||||||
|
|
||||||
const actionsColumn: ColumnDef<LocalTarget> = {
|
const actionsColumn: ColumnDef<LocalTarget> = {
|
||||||
id: "actions",
|
id: "actions",
|
||||||
header: () => (<span className="p-3">{t("actions")}</span>),
|
header: () => <span className="p-3">{t("actions")}</span>,
|
||||||
cell: ({ row }) => (
|
cell: ({ row }) => (
|
||||||
<div className="flex items-center justify-end w-full">
|
<div className="flex items-center justify-end w-full">
|
||||||
<Button
|
<Button
|
||||||
@@ -1341,42 +1344,38 @@ export default function Page() {
|
|||||||
</form>
|
</form>
|
||||||
</Form>
|
</Form>
|
||||||
</SettingsSectionForm>
|
</SettingsSectionForm>
|
||||||
|
|
||||||
|
{resourceTypes.length > 1 && (
|
||||||
|
<>
|
||||||
|
<div className="mb-2">
|
||||||
|
<span className="text-sm font-medium">
|
||||||
|
{t("type")}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<StrategySelect
|
||||||
|
options={resourceTypes}
|
||||||
|
defaultValue="http"
|
||||||
|
onChange={(value) => {
|
||||||
|
baseForm.setValue(
|
||||||
|
"http",
|
||||||
|
value === "http"
|
||||||
|
);
|
||||||
|
// Update method default when switching resource type
|
||||||
|
addTargetForm.setValue(
|
||||||
|
"method",
|
||||||
|
value === "http"
|
||||||
|
? "http"
|
||||||
|
: null
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
cols={2}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</SettingsSectionBody>
|
</SettingsSectionBody>
|
||||||
</SettingsSection>
|
</SettingsSection>
|
||||||
|
|
||||||
{resourceTypes.length > 1 && (
|
|
||||||
<SettingsSection>
|
|
||||||
<SettingsSectionHeader>
|
|
||||||
<SettingsSectionTitle>
|
|
||||||
{t("resourceType")}
|
|
||||||
</SettingsSectionTitle>
|
|
||||||
<SettingsSectionDescription>
|
|
||||||
{t("resourceTypeDescription")}
|
|
||||||
</SettingsSectionDescription>
|
|
||||||
</SettingsSectionHeader>
|
|
||||||
<SettingsSectionBody>
|
|
||||||
<StrategySelect
|
|
||||||
options={resourceTypes}
|
|
||||||
defaultValue="http"
|
|
||||||
onChange={(value) => {
|
|
||||||
baseForm.setValue(
|
|
||||||
"http",
|
|
||||||
value === "http"
|
|
||||||
);
|
|
||||||
// Update method default when switching resource type
|
|
||||||
addTargetForm.setValue(
|
|
||||||
"method",
|
|
||||||
value === "http"
|
|
||||||
? "http"
|
|
||||||
: null
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
cols={2}
|
|
||||||
/>
|
|
||||||
</SettingsSectionBody>
|
|
||||||
</SettingsSection>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{baseForm.watch("http") ? (
|
{baseForm.watch("http") ? (
|
||||||
<SettingsSection>
|
<SettingsSection>
|
||||||
<SettingsSectionHeader>
|
<SettingsSectionHeader>
|
||||||
@@ -1422,146 +1421,98 @@ export default function Page() {
|
|||||||
</SettingsSectionDescription>
|
</SettingsSectionDescription>
|
||||||
</SettingsSectionHeader>
|
</SettingsSectionHeader>
|
||||||
<SettingsSectionBody>
|
<SettingsSectionBody>
|
||||||
<SettingsSectionForm>
|
<Form {...tcpUdpForm}>
|
||||||
<Form {...tcpUdpForm}>
|
<form
|
||||||
<form
|
onKeyDown={(e) => {
|
||||||
onKeyDown={(e) => {
|
if (e.key === "Enter") {
|
||||||
if (e.key === "Enter") {
|
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"
|
||||||
className="space-y-4"
|
id="tcp-udp-settings-form"
|
||||||
id="tcp-udp-settings-form"
|
>
|
||||||
>
|
<Controller
|
||||||
<Controller
|
control={tcpUdpForm.control}
|
||||||
control={
|
name="protocol"
|
||||||
tcpUdpForm.control
|
render={({ field }) => (
|
||||||
}
|
<FormItem>
|
||||||
name="protocol"
|
<FormLabel>
|
||||||
render={({ field }) => (
|
{t("protocol")}
|
||||||
<FormItem>
|
</FormLabel>
|
||||||
<FormLabel>
|
<Select
|
||||||
{t(
|
onValueChange={
|
||||||
"protocol"
|
field.onChange
|
||||||
)}
|
}
|
||||||
</FormLabel>
|
{...field}
|
||||||
<Select
|
>
|
||||||
onValueChange={
|
|
||||||
field.onChange
|
|
||||||
}
|
|
||||||
{...field}
|
|
||||||
>
|
|
||||||
<FormControl>
|
|
||||||
<SelectTrigger>
|
|
||||||
<SelectValue
|
|
||||||
placeholder={t(
|
|
||||||
"protocolSelect"
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</SelectTrigger>
|
|
||||||
</FormControl>
|
|
||||||
<SelectContent>
|
|
||||||
<SelectItem value="tcp">
|
|
||||||
TCP
|
|
||||||
</SelectItem>
|
|
||||||
<SelectItem value="udp">
|
|
||||||
UDP
|
|
||||||
</SelectItem>
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
<FormMessage />
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<FormField
|
|
||||||
control={
|
|
||||||
tcpUdpForm.control
|
|
||||||
}
|
|
||||||
name="proxyPort"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormItem>
|
|
||||||
<FormLabel>
|
|
||||||
{t(
|
|
||||||
"resourcePortNumber"
|
|
||||||
)}
|
|
||||||
</FormLabel>
|
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input
|
<SelectTrigger>
|
||||||
type="number"
|
<SelectValue
|
||||||
value={
|
placeholder={t(
|
||||||
field.value ??
|
"protocolSelect"
|
||||||
""
|
)}
|
||||||
}
|
|
||||||
onChange={(
|
|
||||||
e
|
|
||||||
) =>
|
|
||||||
field.onChange(
|
|
||||||
e
|
|
||||||
.target
|
|
||||||
.value
|
|
||||||
? parseInt(
|
|
||||||
e
|
|
||||||
.target
|
|
||||||
.value
|
|
||||||
)
|
|
||||||
: undefined
|
|
||||||
)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</FormControl>
|
|
||||||
<FormMessage />
|
|
||||||
<FormDescription>
|
|
||||||
{t(
|
|
||||||
"resourcePortNumberDescription"
|
|
||||||
)}
|
|
||||||
</FormDescription>
|
|
||||||
</FormItem>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/* {build == "oss" && (
|
|
||||||
<FormField
|
|
||||||
control={
|
|
||||||
tcpUdpForm.control
|
|
||||||
}
|
|
||||||
name="enableProxy"
|
|
||||||
render={({
|
|
||||||
field
|
|
||||||
}) => (
|
|
||||||
<FormItem className="flex flex-row items-start space-x-3 space-y-0">
|
|
||||||
<FormControl>
|
|
||||||
<Checkbox
|
|
||||||
variant={
|
|
||||||
"outlinePrimarySquare"
|
|
||||||
}
|
|
||||||
checked={
|
|
||||||
field.value
|
|
||||||
}
|
|
||||||
onCheckedChange={
|
|
||||||
field.onChange
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</SelectTrigger>
|
||||||
<div className="space-y-1 leading-none">
|
</FormControl>
|
||||||
<FormLabel>
|
<SelectContent>
|
||||||
{t(
|
<SelectItem value="tcp">
|
||||||
"resourceEnableProxy"
|
TCP
|
||||||
)}
|
</SelectItem>
|
||||||
</FormLabel>
|
<SelectItem value="udp">
|
||||||
<FormDescription>
|
UDP
|
||||||
{t(
|
</SelectItem>
|
||||||
"resourceEnableProxyDescription"
|
</SelectContent>
|
||||||
)}
|
</Select>
|
||||||
</FormDescription>
|
<FormMessage />
|
||||||
</div>
|
</FormItem>
|
||||||
</FormItem>
|
)}
|
||||||
)}
|
/>
|
||||||
/>
|
|
||||||
)} */}
|
<FormField
|
||||||
</form>
|
control={tcpUdpForm.control}
|
||||||
</Form>
|
name="proxyPort"
|
||||||
</SettingsSectionForm>
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>
|
||||||
|
{t(
|
||||||
|
"resourcePortNumber"
|
||||||
|
)}
|
||||||
|
</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input
|
||||||
|
type="number"
|
||||||
|
value={
|
||||||
|
field.value ??
|
||||||
|
""
|
||||||
|
}
|
||||||
|
onChange={(
|
||||||
|
e
|
||||||
|
) =>
|
||||||
|
field.onChange(
|
||||||
|
e
|
||||||
|
.target
|
||||||
|
.value
|
||||||
|
? parseInt(
|
||||||
|
e
|
||||||
|
.target
|
||||||
|
.value
|
||||||
|
)
|
||||||
|
: undefined
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
<FormDescription>
|
||||||
|
{t(
|
||||||
|
"resourcePortNumberDescription"
|
||||||
|
)}
|
||||||
|
</FormDescription>
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</form>
|
||||||
|
</Form>
|
||||||
</SettingsSectionBody>
|
</SettingsSectionBody>
|
||||||
</SettingsSection>
|
</SettingsSection>
|
||||||
)}
|
)}
|
||||||
@@ -1596,13 +1547,21 @@ export default function Page() {
|
|||||||
(
|
(
|
||||||
header
|
header
|
||||||
) => {
|
) => {
|
||||||
const isActionsColumn = header.column.id === "actions";
|
const isActionsColumn =
|
||||||
|
header
|
||||||
|
.column
|
||||||
|
.id ===
|
||||||
|
"actions";
|
||||||
return (
|
return (
|
||||||
<TableHead
|
<TableHead
|
||||||
key={
|
key={
|
||||||
header.id
|
header.id
|
||||||
}
|
}
|
||||||
className={isActionsColumn ? "sticky right-0 z-10 w-auto min-w-fit bg-card" : ""}
|
className={
|
||||||
|
isActionsColumn
|
||||||
|
? "sticky right-0 z-10 w-auto min-w-fit bg-card"
|
||||||
|
: ""
|
||||||
|
}
|
||||||
>
|
>
|
||||||
{header.isPlaceholder
|
{header.isPlaceholder
|
||||||
? null
|
? null
|
||||||
@@ -1639,13 +1598,21 @@ export default function Page() {
|
|||||||
(
|
(
|
||||||
cell
|
cell
|
||||||
) => {
|
) => {
|
||||||
const isActionsColumn = cell.column.id === "actions";
|
const isActionsColumn =
|
||||||
|
cell
|
||||||
|
.column
|
||||||
|
.id ===
|
||||||
|
"actions";
|
||||||
return (
|
return (
|
||||||
<TableCell
|
<TableCell
|
||||||
key={
|
key={
|
||||||
cell.id
|
cell.id
|
||||||
}
|
}
|
||||||
className={isActionsColumn ? "sticky right-0 z-10 w-auto min-w-fit bg-card" : ""}
|
className={
|
||||||
|
isActionsColumn
|
||||||
|
? "sticky right-0 z-10 w-auto min-w-fit bg-card"
|
||||||
|
: ""
|
||||||
|
}
|
||||||
>
|
>
|
||||||
{flexRender(
|
{flexRender(
|
||||||
cell
|
cell
|
||||||
@@ -1711,7 +1678,7 @@ export default function Page() {
|
|||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<div className="text-center py-8 border-2 border-dashed border-muted rounded-lg p-4">
|
<div className="text-center p-4">
|
||||||
<p className="text-muted-foreground mb-4">
|
<p className="text-muted-foreground mb-4">
|
||||||
{t("targetNoOne")}
|
{t("targetNoOne")}
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@@ -809,15 +809,6 @@ export default function DomainPicker2({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{loadingDomains && (
|
|
||||||
<div className="flex items-center justify-center p-4">
|
|
||||||
<div className="flex items-center space-x-2 text-sm text-muted-foreground">
|
|
||||||
<div className="animate-spin rounded-full h-4 w-4 border-b-2 border-primary"></div>
|
|
||||||
<span>{t("domainPickerLoadingDomains")}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user