clean up targets input a little

This commit is contained in:
miloschwartz
2025-12-06 21:00:51 -05:00
parent e24a13fb11
commit 56d30ad6bd
4 changed files with 284 additions and 279 deletions

View File

@@ -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",

View File

@@ -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) {

View File

@@ -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>

View File

@@ -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>
); );
} }