mirror of
https://github.com/fosrl/pangolin.git
synced 2026-03-04 09:46:40 +00:00
Small ui adjustments
This commit is contained in:
@@ -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>
|
||||||
)
|
)
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -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 || ""}>
|
||||||
|
|||||||
Reference in New Issue
Block a user