"use client";
import CopyToClipboard from "@app/components/CopyToClipboard";
import {
InfoSection,
InfoSectionContent,
InfoSections,
InfoSectionTitle
} from "@app/components/InfoSection";
import {
SettingsSection,
SettingsSectionBody,
SettingsSectionDescription,
SettingsSectionHeader,
SettingsSectionTitle
} from "@app/components/Settings";
import {
SidePanel,
SidePanelBody,
SidePanelContent,
SidePanelFooter,
SidePanelHeader,
SidePanelTitle
} from "@app/components/SidePanel";
import { Alert, AlertDescription, AlertTitle } from "@app/components/ui/alert";
import { Button } from "@app/components/ui/button";
import {
derivePublicAuthState,
formatPortRestrictionDisplay,
formatPublicResourceType
} from "@app/lib/launcherResourceDetails";
import { getLauncherResourceAdminHref } from "@app/lib/launcherResourceAdminHref";
import {
formatSiteResourceDestinationDisplay,
isSafeUrlForLink
} from "@app/lib/launcherResourceAccess";
import { launcherQueries } from "@app/lib/queries";
import type { LauncherResource } from "@server/routers/launcher/types";
import type { GetResourceAuthInfoResponse } from "@server/routers/resource/getResourceAuthInfo";
import type { GetResourceResponse } from "@server/routers/resource/getResource";
import type { GetSiteResourceResponse } from "@server/routers/siteResource/getSiteResource";
import { useQuery } from "@tanstack/react-query";
import {
AlertCircle,
CheckCircle2,
Clock,
ExternalLink,
Loader2,
ShieldCheck,
ShieldOff,
XCircle
} from "lucide-react";
import { useTranslations } from "next-intl";
import Link from "next/link";
type LauncherResourcePanelProps = {
open: boolean;
onOpenChange: (open: boolean) => void;
resource: LauncherResource | null;
orgId: string;
isAdmin: boolean;
};
type LauncherResourceDetailResult =
| {
resourceType: "public";
data: GetResourceResponse;
authInfo: GetResourceAuthInfoResponse;
}
| { resourceType: "site"; data: GetSiteResourceResponse };
function AccessMethodContent({
accessDisplay,
accessCopyValue,
accessUrl
}: {
accessDisplay: string;
accessCopyValue: string;
accessUrl?: string | null;
}) {
if (!accessDisplay) {
return -;
}
const href = accessUrl ?? undefined;
const canLink = Boolean(href && isSafeUrlForLink(href));
if (canLink && href) {
return (
);
}
return (
);
}
function HealthStatusDisplay({
health
}: {
health: string | null | undefined;
}) {
const t = useTranslations();
const status = health ?? "unknown";
if (status === "healthy") {
return (
{t("resourcesTableHealthy")}
);
}
if (status === "degraded") {
return (
{t("resourcesTableDegraded")}
);
}
if (status === "unhealthy") {
return (
{t("resourcesTableUnhealthy")}
);
}
return (
{t("resourcesTableUnknown")}
);
}
const PUBLIC_AUTH_BROWSER_MODES = ["http", "ssh", "rdp", "vnc"];
function AuthMethodStatusDisplay({ enabled }: { enabled: boolean }) {
const t = useTranslations();
return (
{enabled ? (
) : (
)}
{enabled ? t("enabled") : t("disabled")}
);
}
function PublicResourceAuthMethods({
authInfo
}: {
authInfo: GetResourceAuthInfoResponse;
}) {
const t = useTranslations();
const authMethods = [
{
key: "sso",
title: t("policyAuthSsoTitle"),
enabled: authInfo.sso
},
{
key: "password",
title: t("policyAuthPasscodeTitle"),
enabled: authInfo.password
},
{
key: "pincode",
title: t("policyAuthPincodeTitle"),
enabled: authInfo.pincode
},
{
key: "whitelist",
title: t("policyAuthEmailTitle"),
enabled: authInfo.whitelist
},
{
key: "headerAuth",
title: t("policyAuthHeaderAuthTitle"),
enabled: authInfo.headerAuth
}
];
return (
{t("authentication")}
{t("resourceLauncherAuthMethodsDescription")}
{authMethods.map((method) => (
{method.title}
))}
);
}
function PublicResourceDetails({
launcherResource,
resource,
authInfo
}: {
launcherResource: LauncherResource;
resource: GetResourceResponse;
authInfo: GetResourceAuthInfoResponse;
}) {
const t = useTranslations();
const supportsAuth = PUBLIC_AUTH_BROWSER_MODES.includes(
resource.mode || ""
);
const authState = derivePublicAuthState(resource.mode, authInfo);
const infoSectionCount = supportsAuth ? 4 : 3;
return (
{t("resourceLauncherResourceDetails")}
{t("resourceLauncherResourceDetailsDescription")}
{t("type")}
{formatPublicResourceType(resource)}
{t("access")}
{supportsAuth ? (
{t("authentication")}
{authState === "protected" ? (
{t("protected")}
) : (
{t("notProtected")}
)}
) : null}
{t("health")}
{supportsAuth ? (
) : null}
);
}
function PrivateResourceDetails({
launcherResource,
resource
}: {
launcherResource: LauncherResource;
resource: GetSiteResourceResponse;
}) {
const t = useTranslations();
const modeLabel: Record = {
host: t("editInternalResourceDialogModeHost"),
cidr: t("editInternalResourceDialogModeCidr"),
http: t("editInternalResourceDialogModeHttp"),
ssh: t("editInternalResourceDialogModeSsh")
};
const destination = formatSiteResourceDestinationDisplay({
mode: resource.mode,
destination: resource.destination,
destinationPort: resource.destinationPort,
scheme: resource.scheme
});
const portRestrictions = formatPortRestrictionDisplay(resource);
const showAlias = resource.mode !== "cidr" && resource.mode !== "http";
const showDestination = !(
resource.mode === "ssh" && resource.authDaemonMode === "native"
);
const infoSectionCount =
2 + (showDestination ? 1 : 0) + (showAlias ? 1 : 0) + 1;
return (
{t("resourceLauncherPrivateClientRequiredTitle")}
{t("resourceLauncherPrivateClientRequired")}{" "}
{t("resourceLauncherDownloadClient")}
{t("resourceLauncherResourceDetails")}
{t("resourceLauncherResourceDetailsDescription")}
{t("type")}
{modeLabel[resource.mode]}
{t("access")}
{showDestination ? (
{t("editInternalResourceDialogDestination")}
{destination || "-"}
) : null}
{showAlias ? (
{t("editInternalResourceDialogAlias")}
{resource.alias?.trim()
? resource.alias
: "-"}
) : null}
{t("portRestrictions")}
{!portRestrictions.hasNonDefaultPorts ? (
{t(
"resourceLauncherNoPortRestrictions"
)}
) : (
{portRestrictions.tcp.state !==
"all" ? (
{t("resourceLauncherTcp")}:{" "}
{portRestrictions.tcp.state ===
"blocked"
? t("blocked")
: portRestrictions.tcp
.ports}
) : null}
{portRestrictions.udp.state !==
"all" ? (
{t("resourceLauncherUdp")}:{" "}
{portRestrictions.udp.state ===
"blocked"
? t("blocked")
: portRestrictions.udp
.ports}
) : null}
)}
);
}
function LauncherResourcePanelBody({
orgId,
resource,
open
}: {
orgId: string;
resource: LauncherResource;
open: boolean;
}) {
const t = useTranslations();
const { data, isPending, isError } = useQuery({
...launcherQueries.resourceDetail(orgId, resource),
enabled: open
});
if (isPending) {
return (
);
}
if (isError || !data) {
return (
{t("resourceLauncherFailedToLoadDetails")}
);
}
const detail = data as LauncherResourceDetailResult;
if (detail.resourceType === "public") {
return (
);
}
return (
);
}
export function LauncherResourcePanel({
open,
onOpenChange,
resource,
orgId,
isAdmin
}: LauncherResourcePanelProps) {
const t = useTranslations();
return (
{resource?.name ?? ""}
{resource ? (
) : null}
{isAdmin && resource ? (
) : null}
);
}