mirror of
https://github.com/fosrl/pangolin.git
synced 2026-09-30 09:49:05 +02:00
show exit nodes in resource launcher
This commit is contained in:
@@ -4314,9 +4314,12 @@
|
||||
"resourceLauncherAuthMethodsDescription": "Authentication methods enabled for this resource.",
|
||||
"resourceLauncherPrivateClientRequired": "Connect with a client on your device to access this resource privately.",
|
||||
"resourceLauncherPrivateClientRequiredTitle": "Client Connection Required",
|
||||
"resourceLauncherExitNodeTitle": "Exit Node Routing",
|
||||
"resourceLauncherExitNodeDescription": "After connecting your client, pick this exit node in the client menu to route all traffic through the attached sites.",
|
||||
"resourceLauncherDownloadClient": "Download client",
|
||||
"resourceLauncherFailedToLoadDetails": "Could not load resource details. You may no longer have access to this resource.",
|
||||
"resourceLauncherNoPortRestrictions": "No port restrictions",
|
||||
"resourceLauncherAllInternetTraffic": "All internet traffic",
|
||||
"resourceLauncherTcp": "TCP",
|
||||
"resourceLauncherUdp": "UDP",
|
||||
"resourceLauncherUnlabeled": "Unlabeled",
|
||||
|
||||
@@ -125,6 +125,16 @@ export function formatPublicResourceAccess(
|
||||
export function formatSiteResourceAccess(
|
||||
resource: SiteResourceAccessInput
|
||||
): LauncherAccessFields {
|
||||
// Exit node (gateway) destinations are always 0.0.0.0/0 — not useful to
|
||||
// show or copy. The launcher UI renders a localized capability label.
|
||||
if (resource.mode === "gateway") {
|
||||
return {
|
||||
accessDisplay: "",
|
||||
accessCopyValue: "",
|
||||
accessUrl: null
|
||||
};
|
||||
}
|
||||
|
||||
if (
|
||||
(resource.mode === "http" || resource.mode === "inference") &&
|
||||
resource.fullDomain
|
||||
|
||||
@@ -55,12 +55,12 @@ const listAllSiteResourcesByOrgQuerySchema = z.strictObject({
|
||||
}),
|
||||
query: z.string().optional(),
|
||||
mode: z
|
||||
.enum(["host", "cidr", "http", "ssh", "inference"])
|
||||
.enum(["host", "cidr", "http", "ssh", "inference", "gateway"])
|
||||
.optional()
|
||||
.catch(undefined)
|
||||
.openapi({
|
||||
type: "string",
|
||||
enum: ["host", "cidr", "http", "ssh", "inference"],
|
||||
enum: ["host", "cidr", "http", "ssh", "inference", "gateway"],
|
||||
description: "Filter site resources by mode"
|
||||
}),
|
||||
sort_by: z
|
||||
|
||||
@@ -357,6 +357,12 @@ export default function PrivateResourcesTable({
|
||||
label: t(
|
||||
"editInternalResourceDialogModeInference"
|
||||
)
|
||||
},
|
||||
{
|
||||
value: "gateway",
|
||||
label: t(
|
||||
"editInternalResourceDialogModeGateway"
|
||||
)
|
||||
}
|
||||
]}
|
||||
selectedValue={searchParams.get("mode") ?? undefined}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { isSafeUrlForLink } from "@app/lib/launcherResourceAccess";
|
||||
import { useTranslations } from "next-intl";
|
||||
import Link from "next/link";
|
||||
import { LauncherCopyIcon } from "./LauncherCopyIcon";
|
||||
|
||||
@@ -8,6 +9,7 @@ type LauncherResourceAccessProps = {
|
||||
accessDisplay: string;
|
||||
accessCopyValue: string;
|
||||
accessUrl?: string | null;
|
||||
mode?: string;
|
||||
variant: "grid" | "list";
|
||||
};
|
||||
|
||||
@@ -15,15 +17,23 @@ export function LauncherResourceAccess({
|
||||
accessDisplay,
|
||||
accessCopyValue,
|
||||
accessUrl,
|
||||
mode,
|
||||
variant
|
||||
}: LauncherResourceAccessProps) {
|
||||
if (!accessDisplay) {
|
||||
const t = useTranslations();
|
||||
const isExitNode = mode === "gateway";
|
||||
const display = isExitNode
|
||||
? t("resourceLauncherAllInternetTraffic")
|
||||
: accessDisplay;
|
||||
|
||||
if (!display) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const href = accessUrl ?? undefined;
|
||||
const canLink = href && isSafeUrlForLink(href);
|
||||
const canLink = !isExitNode && href && isSafeUrlForLink(href);
|
||||
const copyValue = canLink ? href : accessCopyValue;
|
||||
const showCopy = !isExitNode && Boolean(copyValue);
|
||||
|
||||
if (variant === "list") {
|
||||
return (
|
||||
@@ -35,14 +45,14 @@ export function LauncherResourceAccess({
|
||||
rel="noopener noreferrer"
|
||||
className="min-w-0 truncate text-sm text-muted-foreground hover:underline max-md:overflow-visible max-md:whitespace-nowrap"
|
||||
>
|
||||
{accessDisplay}
|
||||
{display}
|
||||
</Link>
|
||||
) : (
|
||||
<span className="min-w-0 truncate text-sm text-muted-foreground max-md:overflow-visible max-md:whitespace-nowrap">
|
||||
{accessDisplay}
|
||||
{display}
|
||||
</span>
|
||||
)}
|
||||
<LauncherCopyIcon text={copyValue} />
|
||||
{showCopy ? <LauncherCopyIcon text={copyValue} /> : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -56,14 +66,14 @@ export function LauncherResourceAccess({
|
||||
rel="noopener noreferrer"
|
||||
className="min-w-0 flex-1 truncate text-sm text-muted-foreground hover:underline"
|
||||
>
|
||||
{accessDisplay}
|
||||
{display}
|
||||
</Link>
|
||||
) : (
|
||||
<span className="min-w-0 flex-1 truncate text-sm text-muted-foreground">
|
||||
{accessDisplay}
|
||||
{display}
|
||||
</span>
|
||||
)}
|
||||
<LauncherCopyIcon text={copyValue} />
|
||||
{showCopy ? <LauncherCopyIcon text={copyValue} /> : null}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -56,6 +56,7 @@ export function LauncherResourceCard({
|
||||
accessDisplay={resource.accessDisplay}
|
||||
accessCopyValue={resource.accessCopyValue}
|
||||
accessUrl={resource.accessUrl}
|
||||
mode={resource.mode}
|
||||
variant="grid"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -654,6 +654,7 @@ function PrivateResourceDetails({
|
||||
const t = useTranslations();
|
||||
const isInference = resource.mode === "inference";
|
||||
const isSsh = resource.mode === "ssh";
|
||||
const isExitNode = resource.mode === "gateway";
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
@@ -678,6 +679,18 @@ function PrivateResourceDetails({
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
|
||||
{isExitNode ? (
|
||||
<Alert variant="default">
|
||||
<AlertCircle className="size-4" />
|
||||
<AlertTitle>
|
||||
{t("resourceLauncherExitNodeTitle")}
|
||||
</AlertTitle>
|
||||
<AlertDescription>
|
||||
{t("resourceLauncherExitNodeDescription")}
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
) : null}
|
||||
|
||||
<SettingsSection>
|
||||
<SettingsSectionHeader>
|
||||
<SettingsSectionTitle>
|
||||
|
||||
@@ -51,6 +51,7 @@ export function LauncherResourceRow({
|
||||
accessDisplay={resource.accessDisplay}
|
||||
accessCopyValue={resource.accessCopyValue}
|
||||
accessUrl={resource.accessUrl}
|
||||
mode={resource.mode}
|
||||
variant="list"
|
||||
/>
|
||||
|
||||
|
||||
@@ -66,6 +66,16 @@ export function formatPublicResourceAccess(
|
||||
export function formatSiteResourceAccess(
|
||||
resource: SiteResourceAccessInput
|
||||
): LauncherAccessFields {
|
||||
// Exit node (gateway) destinations are always 0.0.0.0/0 — not useful to
|
||||
// show or copy. Callers should render a localized capability label.
|
||||
if (resource.mode === "gateway") {
|
||||
return {
|
||||
accessDisplay: "",
|
||||
accessCopyValue: "",
|
||||
accessUrl: null
|
||||
};
|
||||
}
|
||||
|
||||
if (
|
||||
(resource.mode === "http" || resource.mode === "inference") &&
|
||||
resource.fullDomain
|
||||
|
||||
Reference in New Issue
Block a user