Small ui adjustments

This commit is contained in:
Owen
2025-10-08 16:42:30 -07:00
committed by Pallavi Kumari
parent c0cc81ed96
commit 2f5e6248cd
2 changed files with 161 additions and 117 deletions

View File

@@ -109,7 +109,12 @@ import {
PathRewriteModal PathRewriteModal
} from "@app/components/PathMatchRenameModal"; } from "@app/components/PathMatchRenameModal";
import { Badge } from "@app/components/ui/badge"; import { Badge } from "@app/components/ui/badge";
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@app/components/ui/tooltip"; import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger
} from "@app/components/ui/tooltip";
const addTargetSchema = z const addTargetSchema = z
.object({ .object({
@@ -517,7 +522,7 @@ export default function ReverseProxyTargets(props: {
pathMatchType: null, pathMatchType: null,
rewritePath: null, rewritePath: null,
rewritePathType: null, rewritePathType: null,
priority: 100, priority: 100
}); });
} }
@@ -537,11 +542,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?.type || null siteType: site?.type || null
} }
: target : target
) )
); );
@@ -552,10 +557,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
) )
); );
@@ -695,7 +700,12 @@ export default function ReverseProxyTargets(props: {
<Info className="h-4 w-4 text-muted-foreground" /> <Info className="h-4 w-4 text-muted-foreground" />
</TooltipTrigger> </TooltipTrigger>
<TooltipContent className="max-w-xs"> <TooltipContent className="max-w-xs">
<p>Higher priority routes are evaluated first. Priority = 100 means automatic ordering (system decides). Use another number to enforce manual priority.</p> <p>
Higher priority routes are evaluated first.
Priority = 100 means automatic ordering
(system decides). Use another number to
enforce manual priority.
</p>
</TooltipContent> </TooltipContent>
</Tooltip> </Tooltip>
</TooltipProvider> </TooltipProvider>
@@ -770,8 +780,13 @@ export default function ReverseProxyTargets(props: {
return ( return (
<> <>
{row.original.siteType === "newt" ? ( {row.original.siteType === "newt" ? (
<Button variant="outline" <Button
className="flex items-center gap-2 p-2 max-w-md w-full text-left cursor-pointer"> variant="outline"
className="flex items-center gap-2 p-2 max-w-md w-full text-left cursor-pointer"
onClick={() =>
openHealthCheckDialog(row.original)
}
>
<div className="flex items-center space-x-1"> <div className="flex items-center space-x-1">
<Badge variant={getStatusColor(status)}> <Badge variant={getStatusColor(status)}>
<div className="flex items-center gap-1"> <div className="flex items-center gap-1">
@@ -779,22 +794,21 @@ export default function ReverseProxyTargets(props: {
{getStatusText(status)} {getStatusText(status)}
</div> </div>
</Badge> </Badge>
<Button
variant="text"
size="sm"
onClick={() =>
openHealthCheckDialog(row.original)
}
className="h-6 w-6 p-0"
>
<Settings className="h-4 w-4" /> <Settings className="h-4 w-4" />
</Button>
</div> </div>
</Button> </Button>
) : ( ) : (
<Badge variant="secondary"> <Button
{t("healthCheckNotAvailable")} variant="outline"
</Badge> disabled={true}
className="flex items-center gap-2 p-2 max-w-md w-full text-left cursor-pointer"
>
<div className="flex items-center space-x-1">
<Badge variant="secondary">
{t("healthCheckNotAvailable")}
</Badge>
</div>
</Button>
)} )}
</> </>
); );
@@ -865,7 +879,10 @@ export default function ReverseProxyTargets(props: {
(site) => site.siteId === row.original.siteId (site) => site.siteId === row.original.siteId
); );
const handleContainerSelectForTarget = (hostname: string, port?: number) => { const handleContainerSelectForTarget = (
hostname: string,
port?: number
) => {
updateTarget(row.original.targetId, { updateTarget(row.original.targetId, {
...row.original, ...row.original,
ip: hostname ip: hostname
@@ -880,7 +897,10 @@ export default function ReverseProxyTargets(props: {
return ( return (
<div className="flex items-center gap-1"> <div className="flex items-center gap-1">
<Button variant={"outline"} className="w-full justify-start py-0 space-x-2 px-0 hover:bg-card cursor-default"> <Button
variant={"outline"}
className="w-full justify-start py-0 space-x-2 px-0 hover:bg-card cursor-default"
>
<Popover> <Popover>
<PopoverTrigger asChild> <PopoverTrigger asChild>
<Button <Button
@@ -888,31 +908,46 @@ export default function ReverseProxyTargets(props: {
role="combobox" role="combobox"
className={cn( className={cn(
"min-w-[90px] justify-between text-sm font-medium border-r pr-4 rounded-none h-8 hover:bg-transparent", "min-w-[90px] justify-between text-sm font-medium border-r pr-4 rounded-none h-8 hover:bg-transparent",
!row.original.siteId && "text-muted-foreground" !row.original.siteId &&
"text-muted-foreground"
)} )}
> >
{row.original.siteId ? selectedSite?.name : t("siteSelect")} {row.original.siteId
? selectedSite?.name
: t("siteSelect")}
<CaretSortIcon className="ml-2h-4 w-4 shrink-0 opacity-50" /> <CaretSortIcon className="ml-2h-4 w-4 shrink-0 opacity-50" />
</Button> </Button>
</PopoverTrigger> </PopoverTrigger>
<PopoverContent className="p-0 w-[180px]"> <PopoverContent className="p-0 w-[180px]">
<Command> <Command>
<CommandInput placeholder={t("siteSearch")} /> <CommandInput
placeholder={t("siteSearch")}
/>
<CommandList> <CommandList>
<CommandEmpty>{t("siteNotFound")}</CommandEmpty> <CommandEmpty>
{t("siteNotFound")}
</CommandEmpty>
<CommandGroup> <CommandGroup>
{sites.map((site) => ( {sites.map((site) => (
<CommandItem <CommandItem
key={site.siteId} key={site.siteId}
value={`${site.siteId}:${site.name}`} value={`${site.siteId}:${site.name}`}
onSelect={() => onSelect={() =>
updateTarget(row.original.targetId, { siteId: site.siteId }) updateTarget(
row.original
.targetId,
{
siteId: site.siteId
}
)
} }
> >
<CheckIcon <CheckIcon
className={cn( className={cn(
"mr-2 h-4 w-4", "mr-2 h-4 w-4",
site.siteId === row.original.siteId site.siteId ===
row.original
.siteId
? "opacity-100" ? "opacity-100"
: "opacity-0" : "opacity-0"
)} )}
@@ -926,34 +961,36 @@ export default function ReverseProxyTargets(props: {
</PopoverContent> </PopoverContent>
</Popover> </Popover>
{selectedSite && {selectedSite &&
selectedSite.type === "newt" && selectedSite.type === "newt" &&
(() => { (() => {
const dockerState = getDockerStateForSite( const dockerState = getDockerStateForSite(
selectedSite.siteId selectedSite.siteId
); );
return ( return (
<ContainersSelector <ContainersSelector
site={selectedSite} site={selectedSite}
containers={dockerState.containers} containers={dockerState.containers}
isAvailable={dockerState.isAvailable} isAvailable={
onContainerSelect={ dockerState.isAvailable
handleContainerSelectForTarget }
} onContainerSelect={
onRefresh={() => handleContainerSelectForTarget
refreshContainersForSite( }
selectedSite.siteId onRefresh={() =>
) refreshContainersForSite(
} selectedSite.siteId
/> )
); }
})()} />
);
})()}
<Select <Select
defaultValue={row.original.method ?? "http"} defaultValue={row.original.method ?? "http"}
onValueChange={(value) => onValueChange={(value) =>
updateTarget(row.original.targetId, { updateTarget(row.original.targetId, {
...row.original, ...row.original,
method: value, method: value
}) })
} }
> >
@@ -977,27 +1014,34 @@ export default function ReverseProxyTargets(props: {
className="min-w-[130px] border-none placeholder-gray-400" className="min-w-[130px] border-none placeholder-gray-400"
onBlur={(e) => { onBlur={(e) => {
const input = e.target.value.trim(); const input = e.target.value.trim();
const hasProtocol = /^(https?|h2c):\/\//.test(input); const hasProtocol =
/^(https?|h2c):\/\//.test(input);
const hasPort = /:\d+(?:\/|$)/.test(input); const hasPort = /:\d+(?:\/|$)/.test(input);
if (hasProtocol || hasPort) { if (hasProtocol || hasPort) {
const parsed = parseHostTarget(input); const parsed = parseHostTarget(input);
if (parsed) { if (parsed) {
updateTarget(row.original.targetId, { updateTarget(
...row.original, row.original.targetId,
method: hasProtocol {
? parsed.protocol ...row.original,
: row.original.method, method: hasProtocol
ip: parsed.host, ? parsed.protocol
port: hasPort : row.original.method,
? parsed.port ip: parsed.host,
: row.original.port port: hasPort
}); ? parsed.port
: row.original.port
}
);
} else { } else {
updateTarget(row.original.targetId, { updateTarget(
...row.original, row.original.targetId,
ip: input {
}); ...row.original,
ip: input
}
);
} }
} else { } else {
updateTarget(row.original.targetId, { updateTarget(row.original.targetId, {
@@ -1013,7 +1057,7 @@ export default function ReverseProxyTargets(props: {
<Input <Input
placeholder="Port" placeholder="Port"
defaultValue={row.original.port} defaultValue={row.original.port}
className="min-w-[60px] pl-0 border-none placeholder-gray-400" className="w-[120px] pl-0 border-none placeholder-gray-400"
onBlur={(e) => onBlur={(e) =>
updateTarget(row.original.targetId, { updateTarget(row.original.targetId, {
...row.original, ...row.original,
@@ -1184,21 +1228,21 @@ export default function ReverseProxyTargets(props: {
className={cn( className={cn(
"justify-between flex-1", "justify-between flex-1",
!field.value && !field.value &&
"text-muted-foreground" "text-muted-foreground"
)} )}
> >
{field.value {field.value
? sites.find( ? sites.find(
( (
site site
) => ) =>
site.siteId === site.siteId ===
field.value field.value
) )
?.name ?.name
: t( : t(
"siteSelect" "siteSelect"
)} )}
<CaretSortIcon className="ml-2 h-4 w-4 shrink-0 opacity-50" /> <CaretSortIcon className="ml-2 h-4 w-4 shrink-0 opacity-50" />
</Button> </Button>
</FormControl> </FormControl>
@@ -1264,34 +1308,34 @@ export default function ReverseProxyTargets(props: {
); );
return selectedSite && return selectedSite &&
selectedSite.type === selectedSite.type ===
"newt" "newt"
? (() => { ? (() => {
const dockerState = const dockerState =
getDockerStateForSite( getDockerStateForSite(
selectedSite.siteId selectedSite.siteId
); );
return ( return (
<ContainersSelector <ContainersSelector
site={ site={
selectedSite selectedSite
} }
containers={ containers={
dockerState.containers dockerState.containers
} }
isAvailable={ isAvailable={
dockerState.isAvailable dockerState.isAvailable
} }
onContainerSelect={ onContainerSelect={
handleContainerSelect handleContainerSelect
} }
onRefresh={() => onRefresh={() =>
refreshContainersForSite( refreshContainersForSite(
selectedSite.siteId selectedSite.siteId
) )
} }
/> />
); );
})() })()
: null; : null;
})()} })()}
</div> </div>
@@ -1519,12 +1563,12 @@ export default function ReverseProxyTargets(props: {
{header.isPlaceholder {header.isPlaceholder
? null ? null
: flexRender( : flexRender(
header header
.column .column
.columnDef .columnDef
.header, .header,
header.getContext() header.getContext()
)} )}
</TableHead> </TableHead>
) )
)} )}

View File

@@ -250,7 +250,7 @@ export function PathMatchDisplay({
return ( return (
<div className="flex items-center gap-2 w-full text-left"> <div className="flex items-center gap-2 w-full text-left">
<Badge variant="secondary" className="font-mono text-xs shrink-0"> <Badge variant="secondary" className="text-xs shrink-0">
{getTypeLabel(value.pathMatchType)} {getTypeLabel(value.pathMatchType)}
</Badge> </Badge>
<code className="text-sm flex-1 truncate" title={value.path}> <code className="text-sm flex-1 truncate" title={value.path}>
@@ -281,7 +281,7 @@ export function PathRewriteDisplay({
return ( return (
<div className="flex items-center gap-2 w-full text-left"> <div className="flex items-center gap-2 w-full text-left">
<Badge variant="secondary" className="font-mono text-xs shrink-0"> <Badge variant="secondary" className="text-xs shrink-0">
{getTypeLabel(value.rewritePathType)} {getTypeLabel(value.rewritePathType)}
</Badge> </Badge>
<code className="text-sm flex-1 truncate" title={value.rewritePath || ""}> <code className="text-sm flex-1 truncate" title={value.rewritePath || ""}>