From 6f50fb8a4fa56c095bbec21c0a047317646467f9 Mon Sep 17 00:00:00 2001 From: miloschwartz Date: Wed, 17 Dec 2025 16:50:39 -0500 Subject: [PATCH] remote node changes --- messages/en-US.json | 16 +++-- server/routers/site/listSites.ts | 8 ++- .../[remoteExitNodeId]/credentials/page.tsx | 11 ++- .../[remoteExitNodeId]/layout.tsx | 2 +- .../(private)/remote-exit-nodes/page.tsx | 4 +- src/app/[orgId]/settings/sites/page.tsx | 3 +- src/components/ConfirmDeleteDialog.tsx | 7 +- src/components/ExitNodeInfoCard.tsx | 2 +- .../ExitNodesDataTable.tsx | 10 ++- .../ExitNodesTable.tsx | 14 ++-- src/components/HorizontalTabs.tsx | 3 +- src/components/SitesTable.tsx | 68 +++++++++++++------ 12 files changed, 103 insertions(+), 45 deletions(-) rename src/{app/[orgId]/settings/(private)/remote-exit-nodes => components}/ExitNodesDataTable.tsx (75%) rename src/{app/[orgId]/settings/(private)/remote-exit-nodes => components}/ExitNodesTable.tsx (96%) diff --git a/messages/en-US.json b/messages/en-US.json index 6042fd66..80846615 100644 --- a/messages/en-US.json +++ b/messages/en-US.json @@ -100,6 +100,7 @@ "siteTunnelDescription": "Determine how you want to connect to the site", "siteNewtCredentials": "Credentials", "siteNewtCredentialsDescription": "This is how the site will authenticate with the server", + "remoteNodeCredentialsDescription": "This is how the remote node will authenticate with the server", "siteCredentialsSave": "Save the Credentials", "siteCredentialsSaveDescription": "You will only be able to see this once. Make sure to copy it to a secure place.", "siteInfo": "Site Information", @@ -1671,7 +1672,7 @@ "autoLoginErrorNoRedirectUrl": "No redirect URL received from the identity provider.", "autoLoginErrorGeneratingUrl": "Failed to generate authentication URL.", "remoteExitNodeManageRemoteExitNodes": "Remote Nodes", - "remoteExitNodeDescription": "Self-host one or more remote nodes to extend network connectivity and reduce reliance on the cloud", + "remoteExitNodeDescription": "Self-host your own remote relay and proxy server nodes", "remoteExitNodes": "Nodes", "searchRemoteExitNodes": "Search nodes...", "remoteExitNodeAdd": "Add Node", @@ -1681,20 +1682,22 @@ "remoteExitNodeConfirmDelete": "Confirm Delete Node", "remoteExitNodeDelete": "Delete Node", "sidebarRemoteExitNodes": "Remote Nodes", + "remoteExitNodeId": "ID", + "remoteExitNodeSecretKey": "Secret", "remoteExitNodeCreate": { - "title": "Create Node", - "description": "Create a new node to extend network connectivity", + "title": "Create Remote Node", + "description": "Create a new self-hosted remote relay and proxy server node", "viewAllButton": "View All Nodes", "strategy": { "title": "Creation Strategy", - "description": "Choose this to manually configure the node or generate new credentials.", + "description": "Select how you want to create the remote node", "adopt": { "title": "Adopt Node", "description": "Choose this if you already have the credentials for the node." }, "generate": { "title": "Generate Keys", - "description": "Choose this if you want to generate new keys for the node" + "description": "Choose this if you want to generate new keys for the node." } }, "adopt": { @@ -2308,5 +2311,6 @@ "organizationLoginPageTitle": "Organization Login Page", "organizationLoginPageDescription": "Customize the login page for this organization", "resourceLoginPageTitle": "Resource Login Page", - "resourceLoginPageDescription": "Customize the login page for individual resources" + "resourceLoginPageDescription": "Customize the login page for individual resources", + "enterConfirmation": "Enter confirmation" } diff --git a/server/routers/site/listSites.ts b/server/routers/site/listSites.ts index 37ca8fe4..e7a3bb37 100644 --- a/server/routers/site/listSites.ts +++ b/server/routers/site/listSites.ts @@ -1,5 +1,6 @@ import { db, exitNodes, newts } from "@server/db"; import { orgs, roleSites, sites, userSites } from "@server/db"; +import { remoteExitNodes } from "@server/db"; import logger from "@server/logger"; import HttpCode from "@server/types/HttpCode"; import response from "@server/lib/response"; @@ -104,12 +105,17 @@ function querySites(orgId: string, accessibleSiteIds: number[]) { newtVersion: newts.version, exitNodeId: sites.exitNodeId, exitNodeName: exitNodes.name, - exitNodeEndpoint: exitNodes.endpoint + exitNodeEndpoint: exitNodes.endpoint, + remoteExitNodeId: remoteExitNodes.remoteExitNodeId }) .from(sites) .leftJoin(orgs, eq(sites.orgId, orgs.orgId)) .leftJoin(newts, eq(newts.siteId, sites.siteId)) .leftJoin(exitNodes, eq(exitNodes.exitNodeId, sites.exitNodeId)) + .leftJoin( + remoteExitNodes, + eq(remoteExitNodes.exitNodeId, sites.exitNodeId) + ) .where( and( inArray(sites.siteId, accessibleSiteIds), diff --git a/src/app/[orgId]/settings/(private)/remote-exit-nodes/[remoteExitNodeId]/credentials/page.tsx b/src/app/[orgId]/settings/(private)/remote-exit-nodes/[remoteExitNodeId]/credentials/page.tsx index de24e07d..77707e16 100644 --- a/src/app/[orgId]/settings/(private)/remote-exit-nodes/[remoteExitNodeId]/credentials/page.tsx +++ b/src/app/[orgId]/settings/(private)/remote-exit-nodes/[remoteExitNodeId]/credentials/page.tsx @@ -131,10 +131,10 @@ export default function CredentialsPage() { - {t("generatedcredentials")} + {t("credentials")} - {t("regenerateCredentials")} + {t("remoteNodeCredentialsDescription")} @@ -143,7 +143,7 @@ export default function CredentialsPage() { - {t("endpoint") || "Endpoint"} + {t("endpoint")} - {t("remoteExitNodeId") || - "Remote Exit Node ID"} + {t("remoteExitNodeId")} {displayRemoteExitNodeId ? ( @@ -168,7 +167,7 @@ export default function CredentialsPage() { - {t("secretKey") || "Secret Key"} + {t("remoteExitNodeSecretKey")} {displaySecret ? ( diff --git a/src/app/[orgId]/settings/(private)/remote-exit-nodes/[remoteExitNodeId]/layout.tsx b/src/app/[orgId]/settings/(private)/remote-exit-nodes/[remoteExitNodeId]/layout.tsx index 98af49a6..ec1dea83 100644 --- a/src/app/[orgId]/settings/(private)/remote-exit-nodes/[remoteExitNodeId]/layout.tsx +++ b/src/app/[orgId]/settings/(private)/remote-exit-nodes/[remoteExitNodeId]/layout.tsx @@ -43,7 +43,7 @@ export default async function SettingsLayout(props: SettingsLayoutProps) { return ( <> diff --git a/src/app/[orgId]/settings/(private)/remote-exit-nodes/page.tsx b/src/app/[orgId]/settings/(private)/remote-exit-nodes/page.tsx index 632dc0ad..2da0e0da 100644 --- a/src/app/[orgId]/settings/(private)/remote-exit-nodes/page.tsx +++ b/src/app/[orgId]/settings/(private)/remote-exit-nodes/page.tsx @@ -2,7 +2,9 @@ import { internal } from "@app/lib/api"; import { authCookieHeader } from "@app/lib/api/cookies"; import { ListRemoteExitNodesResponse } from "@server/routers/remoteExitNode/types"; import { AxiosResponse } from "axios"; -import ExitNodesTable, { RemoteExitNodeRow } from "./ExitNodesTable"; +import ExitNodesTable, { + RemoteExitNodeRow +} from "@app/components/ExitNodesTable"; import SettingsSectionTitle from "@app/components/SettingsSectionTitle"; import { getTranslations } from "next-intl/server"; diff --git a/src/app/[orgId]/settings/sites/page.tsx b/src/app/[orgId]/settings/sites/page.tsx index 4c1798bc..71ba9dc3 100644 --- a/src/app/[orgId]/settings/sites/page.tsx +++ b/src/app/[orgId]/settings/sites/page.tsx @@ -53,7 +53,8 @@ export default async function SitesPage(props: SitesPageProps) { newtVersion: site.newtVersion || undefined, newtUpdateAvailable: site.newtUpdateAvailable || false, exitNodeName: site.exitNodeName || undefined, - exitNodeEndpoint: site.exitNodeEndpoint || undefined + exitNodeEndpoint: site.exitNodeEndpoint || undefined, + remoteExitNodeId: (site as any).remoteExitNodeId || undefined }; }); diff --git a/src/components/ConfirmDeleteDialog.tsx b/src/components/ConfirmDeleteDialog.tsx index 0ae36bf7..86b25bb2 100644 --- a/src/components/ConfirmDeleteDialog.tsx +++ b/src/components/ConfirmDeleteDialog.tsx @@ -116,7 +116,12 @@ export default function ConfirmDeleteDialog({ render={({ field }) => ( - + diff --git a/src/components/ExitNodeInfoCard.tsx b/src/components/ExitNodeInfoCard.tsx index 8eff6eef..63dff644 100644 --- a/src/components/ExitNodeInfoCard.tsx +++ b/src/components/ExitNodeInfoCard.tsx @@ -19,7 +19,7 @@ export default function ExitNodeInfoCard({}: ExitNodeInfoCardProps) { return ( - + <> diff --git a/src/app/[orgId]/settings/(private)/remote-exit-nodes/ExitNodesDataTable.tsx b/src/components/ExitNodesDataTable.tsx similarity index 75% rename from src/app/[orgId]/settings/(private)/remote-exit-nodes/ExitNodesDataTable.tsx rename to src/components/ExitNodesDataTable.tsx index c12aa9ba..5573c0e4 100644 --- a/src/app/[orgId]/settings/(private)/remote-exit-nodes/ExitNodesDataTable.tsx +++ b/src/components/ExitNodesDataTable.tsx @@ -10,6 +10,8 @@ interface DataTableProps { createRemoteExitNode?: () => void; onRefresh?: () => void; isRefreshing?: boolean; + columnVisibility?: Record; + enableColumnVisibility?: boolean; } export function ExitNodesDataTable({ @@ -17,7 +19,9 @@ export function ExitNodesDataTable({ data, createRemoteExitNode, onRefresh, - isRefreshing + isRefreshing, + columnVisibility, + enableColumnVisibility }: DataTableProps) { const t = useTranslations(); @@ -36,6 +40,10 @@ export function ExitNodesDataTable({ id: "name", desc: false }} + columnVisibility={columnVisibility} + enableColumnVisibility={enableColumnVisibility} + stickyLeftColumn="name" + stickyRightColumn="actions" /> ); } diff --git a/src/app/[orgId]/settings/(private)/remote-exit-nodes/ExitNodesTable.tsx b/src/components/ExitNodesTable.tsx similarity index 96% rename from src/app/[orgId]/settings/(private)/remote-exit-nodes/ExitNodesTable.tsx rename to src/components/ExitNodesTable.tsx index e5250bea..03aa671b 100644 --- a/src/app/[orgId]/settings/(private)/remote-exit-nodes/ExitNodesTable.tsx +++ b/src/components/ExitNodesTable.tsx @@ -2,7 +2,7 @@ import { ColumnDef } from "@tanstack/react-table"; import { ExtendedColumnDef } from "@app/components/ui/data-table"; -import { ExitNodesDataTable } from "./ExitNodesDataTable"; +import { ExitNodesDataTable } from "@app/components/ExitNodesDataTable"; import { DropdownMenu, DropdownMenuContent, @@ -246,12 +246,13 @@ export default function ExitNodesTable({ }, { id: "actions", - header: () => {t("actions")}, + enableHiding: false, + header: () => , cell: ({ row }) => { const nodeRow = row.original; const remoteExitNodeId = nodeRow.id; return ( -
+
@@ -327,6 +328,11 @@ export default function ExitNodesTable({ } onRefresh={refreshData} isRefreshing={isRefreshing} + columnVisibility={{ + type: false, + address: false, + }} + enableColumnVisibility={true} /> ); diff --git a/src/components/HorizontalTabs.tsx b/src/components/HorizontalTabs.tsx index 7cbac226..ddcc2b08 100644 --- a/src/components/HorizontalTabs.tsx +++ b/src/components/HorizontalTabs.tsx @@ -39,7 +39,8 @@ export function HorizontalTabs({ .replace("{niceId}", params.niceId as string) .replace("{userId}", params.userId as string) .replace("{clientId}", params.clientId as string) - .replace("{apiKeyId}", params.apiKeyId as string); + .replace("{apiKeyId}", params.apiKeyId as string) + .replace("{remoteExitNodeId}", params.remoteExitNodeId as string); } return ( diff --git a/src/components/SitesTable.tsx b/src/components/SitesTable.tsx index 46aeb79f..d8f50e99 100644 --- a/src/components/SitesTable.tsx +++ b/src/components/SitesTable.tsx @@ -13,6 +13,7 @@ import { Button } from "@app/components/ui/button"; import { ArrowRight, ArrowUpDown, + ArrowUpRight, Check, MoreHorizontal, X @@ -46,6 +47,7 @@ export type SiteRow = { address?: string; exitNodeName?: string; exitNodeEndpoint?: string; + remoteExitNodeId?: string; }; type SitesTableProps = { @@ -303,27 +305,51 @@ export default function SitesTable({ sites, orgId }: SitesTableProps) { }, cell: ({ row }) => { const originalRow = row.original; - return originalRow.exitNodeName ? ( -
- {originalRow.exitNodeName} - {build == "saas" && - originalRow.exitNodeName && - [ - "mercury", - "venus", - "earth", - "mars", - "jupiter", - "saturn", - "uranus", - "neptune" - ].includes( - originalRow.exitNodeName.toLowerCase() - ) && Cloud} -
- ) : ( - "-" - ); + if (!originalRow.exitNodeName) { + return "-"; + } + + const isCloudNode = + build == "saas" && + originalRow.exitNodeName && + [ + "mercury", + "venus", + "earth", + "mars", + "jupiter", + "saturn", + "uranus", + "neptune" + ].includes(originalRow.exitNodeName.toLowerCase()); + + if (isCloudNode) { + const capitalizedName = + originalRow.exitNodeName.charAt(0).toUpperCase() + + originalRow.exitNodeName.slice(1).toLowerCase(); + return ( + + Pangolin {capitalizedName} + + ); + } + + // Self-hosted node + if (originalRow.remoteExitNodeId) { + return ( + + + + ); + } + + // Fallback if no remoteExitNodeId + return {originalRow.exitNodeName}; } }, {