mirror of
https://github.com/fosrl/pangolin.git
synced 2026-01-28 22:00:51 +00:00
remote node changes
This commit is contained in:
@@ -100,6 +100,7 @@
|
|||||||
"siteTunnelDescription": "Determine how you want to connect to the site",
|
"siteTunnelDescription": "Determine how you want to connect to the site",
|
||||||
"siteNewtCredentials": "Credentials",
|
"siteNewtCredentials": "Credentials",
|
||||||
"siteNewtCredentialsDescription": "This is how the site will authenticate with the server",
|
"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",
|
"siteCredentialsSave": "Save the Credentials",
|
||||||
"siteCredentialsSaveDescription": "You will only be able to see this once. Make sure to copy it to a secure place.",
|
"siteCredentialsSaveDescription": "You will only be able to see this once. Make sure to copy it to a secure place.",
|
||||||
"siteInfo": "Site Information",
|
"siteInfo": "Site Information",
|
||||||
@@ -1671,7 +1672,7 @@
|
|||||||
"autoLoginErrorNoRedirectUrl": "No redirect URL received from the identity provider.",
|
"autoLoginErrorNoRedirectUrl": "No redirect URL received from the identity provider.",
|
||||||
"autoLoginErrorGeneratingUrl": "Failed to generate authentication URL.",
|
"autoLoginErrorGeneratingUrl": "Failed to generate authentication URL.",
|
||||||
"remoteExitNodeManageRemoteExitNodes": "Remote Nodes",
|
"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",
|
"remoteExitNodes": "Nodes",
|
||||||
"searchRemoteExitNodes": "Search nodes...",
|
"searchRemoteExitNodes": "Search nodes...",
|
||||||
"remoteExitNodeAdd": "Add Node",
|
"remoteExitNodeAdd": "Add Node",
|
||||||
@@ -1681,20 +1682,22 @@
|
|||||||
"remoteExitNodeConfirmDelete": "Confirm Delete Node",
|
"remoteExitNodeConfirmDelete": "Confirm Delete Node",
|
||||||
"remoteExitNodeDelete": "Delete Node",
|
"remoteExitNodeDelete": "Delete Node",
|
||||||
"sidebarRemoteExitNodes": "Remote Nodes",
|
"sidebarRemoteExitNodes": "Remote Nodes",
|
||||||
|
"remoteExitNodeId": "ID",
|
||||||
|
"remoteExitNodeSecretKey": "Secret",
|
||||||
"remoteExitNodeCreate": {
|
"remoteExitNodeCreate": {
|
||||||
"title": "Create Node",
|
"title": "Create Remote Node",
|
||||||
"description": "Create a new node to extend network connectivity",
|
"description": "Create a new self-hosted remote relay and proxy server node",
|
||||||
"viewAllButton": "View All Nodes",
|
"viewAllButton": "View All Nodes",
|
||||||
"strategy": {
|
"strategy": {
|
||||||
"title": "Creation 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": {
|
"adopt": {
|
||||||
"title": "Adopt Node",
|
"title": "Adopt Node",
|
||||||
"description": "Choose this if you already have the credentials for the node."
|
"description": "Choose this if you already have the credentials for the node."
|
||||||
},
|
},
|
||||||
"generate": {
|
"generate": {
|
||||||
"title": "Generate Keys",
|
"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": {
|
"adopt": {
|
||||||
@@ -2308,5 +2311,6 @@
|
|||||||
"organizationLoginPageTitle": "Organization Login Page",
|
"organizationLoginPageTitle": "Organization Login Page",
|
||||||
"organizationLoginPageDescription": "Customize the login page for this organization",
|
"organizationLoginPageDescription": "Customize the login page for this organization",
|
||||||
"resourceLoginPageTitle": "Resource Login Page",
|
"resourceLoginPageTitle": "Resource Login Page",
|
||||||
"resourceLoginPageDescription": "Customize the login page for individual resources"
|
"resourceLoginPageDescription": "Customize the login page for individual resources",
|
||||||
|
"enterConfirmation": "Enter confirmation"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { db, exitNodes, newts } from "@server/db";
|
import { db, exitNodes, newts } from "@server/db";
|
||||||
import { orgs, roleSites, sites, userSites } from "@server/db";
|
import { orgs, roleSites, sites, userSites } from "@server/db";
|
||||||
|
import { remoteExitNodes } from "@server/db";
|
||||||
import logger from "@server/logger";
|
import logger from "@server/logger";
|
||||||
import HttpCode from "@server/types/HttpCode";
|
import HttpCode from "@server/types/HttpCode";
|
||||||
import response from "@server/lib/response";
|
import response from "@server/lib/response";
|
||||||
@@ -104,12 +105,17 @@ function querySites(orgId: string, accessibleSiteIds: number[]) {
|
|||||||
newtVersion: newts.version,
|
newtVersion: newts.version,
|
||||||
exitNodeId: sites.exitNodeId,
|
exitNodeId: sites.exitNodeId,
|
||||||
exitNodeName: exitNodes.name,
|
exitNodeName: exitNodes.name,
|
||||||
exitNodeEndpoint: exitNodes.endpoint
|
exitNodeEndpoint: exitNodes.endpoint,
|
||||||
|
remoteExitNodeId: remoteExitNodes.remoteExitNodeId
|
||||||
})
|
})
|
||||||
.from(sites)
|
.from(sites)
|
||||||
.leftJoin(orgs, eq(sites.orgId, orgs.orgId))
|
.leftJoin(orgs, eq(sites.orgId, orgs.orgId))
|
||||||
.leftJoin(newts, eq(newts.siteId, sites.siteId))
|
.leftJoin(newts, eq(newts.siteId, sites.siteId))
|
||||||
.leftJoin(exitNodes, eq(exitNodes.exitNodeId, sites.exitNodeId))
|
.leftJoin(exitNodes, eq(exitNodes.exitNodeId, sites.exitNodeId))
|
||||||
|
.leftJoin(
|
||||||
|
remoteExitNodes,
|
||||||
|
eq(remoteExitNodes.exitNodeId, sites.exitNodeId)
|
||||||
|
)
|
||||||
.where(
|
.where(
|
||||||
and(
|
and(
|
||||||
inArray(sites.siteId, accessibleSiteIds),
|
inArray(sites.siteId, accessibleSiteIds),
|
||||||
|
|||||||
@@ -131,10 +131,10 @@ export default function CredentialsPage() {
|
|||||||
<SettingsSection>
|
<SettingsSection>
|
||||||
<SettingsSectionHeader>
|
<SettingsSectionHeader>
|
||||||
<SettingsSectionTitle>
|
<SettingsSectionTitle>
|
||||||
{t("generatedcredentials")}
|
{t("credentials")}
|
||||||
</SettingsSectionTitle>
|
</SettingsSectionTitle>
|
||||||
<SettingsSectionDescription>
|
<SettingsSectionDescription>
|
||||||
{t("regenerateCredentials")}
|
{t("remoteNodeCredentialsDescription")}
|
||||||
</SettingsSectionDescription>
|
</SettingsSectionDescription>
|
||||||
</SettingsSectionHeader>
|
</SettingsSectionHeader>
|
||||||
<SettingsSectionBody>
|
<SettingsSectionBody>
|
||||||
@@ -143,7 +143,7 @@ export default function CredentialsPage() {
|
|||||||
<InfoSections cols={3}>
|
<InfoSections cols={3}>
|
||||||
<InfoSection>
|
<InfoSection>
|
||||||
<InfoSectionTitle>
|
<InfoSectionTitle>
|
||||||
{t("endpoint") || "Endpoint"}
|
{t("endpoint")}
|
||||||
</InfoSectionTitle>
|
</InfoSectionTitle>
|
||||||
<InfoSectionContent>
|
<InfoSectionContent>
|
||||||
<CopyToClipboard
|
<CopyToClipboard
|
||||||
@@ -153,8 +153,7 @@ export default function CredentialsPage() {
|
|||||||
</InfoSection>
|
</InfoSection>
|
||||||
<InfoSection>
|
<InfoSection>
|
||||||
<InfoSectionTitle>
|
<InfoSectionTitle>
|
||||||
{t("remoteExitNodeId") ||
|
{t("remoteExitNodeId")}
|
||||||
"Remote Exit Node ID"}
|
|
||||||
</InfoSectionTitle>
|
</InfoSectionTitle>
|
||||||
<InfoSectionContent>
|
<InfoSectionContent>
|
||||||
{displayRemoteExitNodeId ? (
|
{displayRemoteExitNodeId ? (
|
||||||
@@ -168,7 +167,7 @@ export default function CredentialsPage() {
|
|||||||
</InfoSection>
|
</InfoSection>
|
||||||
<InfoSection>
|
<InfoSection>
|
||||||
<InfoSectionTitle>
|
<InfoSectionTitle>
|
||||||
{t("secretKey") || "Secret Key"}
|
{t("remoteExitNodeSecretKey")}
|
||||||
</InfoSectionTitle>
|
</InfoSectionTitle>
|
||||||
<InfoSectionContent>
|
<InfoSectionContent>
|
||||||
{displaySecret ? (
|
{displaySecret ? (
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ export default async function SettingsLayout(props: SettingsLayoutProps) {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<SettingsSectionTitle
|
<SettingsSectionTitle
|
||||||
title={`Remote Exit Node ${remoteExitNode?.name || "Unknown"}`}
|
title={`Remote Node ${remoteExitNode?.name || "Unknown"}`}
|
||||||
description="Manage your remote exit node settings and configuration"
|
description="Manage your remote exit node settings and configuration"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,9 @@ import { internal } from "@app/lib/api";
|
|||||||
import { authCookieHeader } from "@app/lib/api/cookies";
|
import { authCookieHeader } from "@app/lib/api/cookies";
|
||||||
import { ListRemoteExitNodesResponse } from "@server/routers/remoteExitNode/types";
|
import { ListRemoteExitNodesResponse } from "@server/routers/remoteExitNode/types";
|
||||||
import { AxiosResponse } from "axios";
|
import { AxiosResponse } from "axios";
|
||||||
import ExitNodesTable, { RemoteExitNodeRow } from "./ExitNodesTable";
|
import ExitNodesTable, {
|
||||||
|
RemoteExitNodeRow
|
||||||
|
} from "@app/components/ExitNodesTable";
|
||||||
import SettingsSectionTitle from "@app/components/SettingsSectionTitle";
|
import SettingsSectionTitle from "@app/components/SettingsSectionTitle";
|
||||||
import { getTranslations } from "next-intl/server";
|
import { getTranslations } from "next-intl/server";
|
||||||
|
|
||||||
|
|||||||
@@ -53,7 +53,8 @@ export default async function SitesPage(props: SitesPageProps) {
|
|||||||
newtVersion: site.newtVersion || undefined,
|
newtVersion: site.newtVersion || undefined,
|
||||||
newtUpdateAvailable: site.newtUpdateAvailable || false,
|
newtUpdateAvailable: site.newtUpdateAvailable || false,
|
||||||
exitNodeName: site.exitNodeName || undefined,
|
exitNodeName: site.exitNodeName || undefined,
|
||||||
exitNodeEndpoint: site.exitNodeEndpoint || undefined
|
exitNodeEndpoint: site.exitNodeEndpoint || undefined,
|
||||||
|
remoteExitNodeId: (site as any).remoteExitNodeId || undefined
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -116,7 +116,12 @@ export default function ConfirmDeleteDialog({
|
|||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input {...field} />
|
<Input
|
||||||
|
{...field}
|
||||||
|
placeholder={t(
|
||||||
|
"enterConfirmation"
|
||||||
|
)}
|
||||||
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ export default function ExitNodeInfoCard({}: ExitNodeInfoCardProps) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Alert>
|
<Alert>
|
||||||
<AlertDescription className="mt-4">
|
<AlertDescription>
|
||||||
<InfoSections cols={2}>
|
<InfoSections cols={2}>
|
||||||
<>
|
<>
|
||||||
<InfoSection>
|
<InfoSection>
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ interface DataTableProps<TData, TValue> {
|
|||||||
createRemoteExitNode?: () => void;
|
createRemoteExitNode?: () => void;
|
||||||
onRefresh?: () => void;
|
onRefresh?: () => void;
|
||||||
isRefreshing?: boolean;
|
isRefreshing?: boolean;
|
||||||
|
columnVisibility?: Record<string, boolean>;
|
||||||
|
enableColumnVisibility?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ExitNodesDataTable<TData, TValue>({
|
export function ExitNodesDataTable<TData, TValue>({
|
||||||
@@ -17,7 +19,9 @@ export function ExitNodesDataTable<TData, TValue>({
|
|||||||
data,
|
data,
|
||||||
createRemoteExitNode,
|
createRemoteExitNode,
|
||||||
onRefresh,
|
onRefresh,
|
||||||
isRefreshing
|
isRefreshing,
|
||||||
|
columnVisibility,
|
||||||
|
enableColumnVisibility
|
||||||
}: DataTableProps<TData, TValue>) {
|
}: DataTableProps<TData, TValue>) {
|
||||||
const t = useTranslations();
|
const t = useTranslations();
|
||||||
|
|
||||||
@@ -36,6 +40,10 @@ export function ExitNodesDataTable<TData, TValue>({
|
|||||||
id: "name",
|
id: "name",
|
||||||
desc: false
|
desc: false
|
||||||
}}
|
}}
|
||||||
|
columnVisibility={columnVisibility}
|
||||||
|
enableColumnVisibility={enableColumnVisibility}
|
||||||
|
stickyLeftColumn="name"
|
||||||
|
stickyRightColumn="actions"
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
import { ColumnDef } from "@tanstack/react-table";
|
import { ColumnDef } from "@tanstack/react-table";
|
||||||
import { ExtendedColumnDef } from "@app/components/ui/data-table";
|
import { ExtendedColumnDef } from "@app/components/ui/data-table";
|
||||||
import { ExitNodesDataTable } from "./ExitNodesDataTable";
|
import { ExitNodesDataTable } from "@app/components/ExitNodesDataTable";
|
||||||
import {
|
import {
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
DropdownMenuContent,
|
DropdownMenuContent,
|
||||||
@@ -246,12 +246,13 @@ export default function ExitNodesTable({
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "actions",
|
id: "actions",
|
||||||
header: () => <span className="p-3">{t("actions")}</span>,
|
enableHiding: false,
|
||||||
|
header: () => <span className="p-3"></span>,
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
const nodeRow = row.original;
|
const nodeRow = row.original;
|
||||||
const remoteExitNodeId = nodeRow.id;
|
const remoteExitNodeId = nodeRow.id;
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2 justify-end">
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger asChild>
|
<DropdownMenuTrigger asChild>
|
||||||
<Button variant="ghost" className="h-8 w-8 p-0">
|
<Button variant="ghost" className="h-8 w-8 p-0">
|
||||||
@@ -283,7 +284,7 @@ export default function ExitNodesTable({
|
|||||||
<Link
|
<Link
|
||||||
href={`/${nodeRow.orgId}/settings/remote-exit-nodes/${remoteExitNodeId}`}
|
href={`/${nodeRow.orgId}/settings/remote-exit-nodes/${remoteExitNodeId}`}
|
||||||
>
|
>
|
||||||
<Button variant={"secondary"} size="sm">
|
<Button variant={"outline"}>
|
||||||
{t("edit")}
|
{t("edit")}
|
||||||
<ArrowRight className="ml-2 w-4 h-4" />
|
<ArrowRight className="ml-2 w-4 h-4" />
|
||||||
</Button>
|
</Button>
|
||||||
@@ -327,6 +328,11 @@ export default function ExitNodesTable({
|
|||||||
}
|
}
|
||||||
onRefresh={refreshData}
|
onRefresh={refreshData}
|
||||||
isRefreshing={isRefreshing}
|
isRefreshing={isRefreshing}
|
||||||
|
columnVisibility={{
|
||||||
|
type: false,
|
||||||
|
address: false,
|
||||||
|
}}
|
||||||
|
enableColumnVisibility={true}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
@@ -39,7 +39,8 @@ export function HorizontalTabs({
|
|||||||
.replace("{niceId}", params.niceId as string)
|
.replace("{niceId}", params.niceId as string)
|
||||||
.replace("{userId}", params.userId as string)
|
.replace("{userId}", params.userId as string)
|
||||||
.replace("{clientId}", params.clientId 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 (
|
return (
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import { Button } from "@app/components/ui/button";
|
|||||||
import {
|
import {
|
||||||
ArrowRight,
|
ArrowRight,
|
||||||
ArrowUpDown,
|
ArrowUpDown,
|
||||||
|
ArrowUpRight,
|
||||||
Check,
|
Check,
|
||||||
MoreHorizontal,
|
MoreHorizontal,
|
||||||
X
|
X
|
||||||
@@ -46,6 +47,7 @@ export type SiteRow = {
|
|||||||
address?: string;
|
address?: string;
|
||||||
exitNodeName?: string;
|
exitNodeName?: string;
|
||||||
exitNodeEndpoint?: string;
|
exitNodeEndpoint?: string;
|
||||||
|
remoteExitNodeId?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
type SitesTableProps = {
|
type SitesTableProps = {
|
||||||
@@ -303,27 +305,51 @@ export default function SitesTable({ sites, orgId }: SitesTableProps) {
|
|||||||
},
|
},
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
const originalRow = row.original;
|
const originalRow = row.original;
|
||||||
return originalRow.exitNodeName ? (
|
if (!originalRow.exitNodeName) {
|
||||||
<div className="flex items-center space-x-2">
|
return "-";
|
||||||
<span>{originalRow.exitNodeName}</span>
|
}
|
||||||
{build == "saas" &&
|
|
||||||
originalRow.exitNodeName &&
|
const isCloudNode =
|
||||||
[
|
build == "saas" &&
|
||||||
"mercury",
|
originalRow.exitNodeName &&
|
||||||
"venus",
|
[
|
||||||
"earth",
|
"mercury",
|
||||||
"mars",
|
"venus",
|
||||||
"jupiter",
|
"earth",
|
||||||
"saturn",
|
"mars",
|
||||||
"uranus",
|
"jupiter",
|
||||||
"neptune"
|
"saturn",
|
||||||
].includes(
|
"uranus",
|
||||||
originalRow.exitNodeName.toLowerCase()
|
"neptune"
|
||||||
) && <Badge variant="secondary">Cloud</Badge>}
|
].includes(originalRow.exitNodeName.toLowerCase());
|
||||||
</div>
|
|
||||||
) : (
|
if (isCloudNode) {
|
||||||
"-"
|
const capitalizedName =
|
||||||
);
|
originalRow.exitNodeName.charAt(0).toUpperCase() +
|
||||||
|
originalRow.exitNodeName.slice(1).toLowerCase();
|
||||||
|
return (
|
||||||
|
<Badge variant="secondary">
|
||||||
|
Pangolin {capitalizedName}
|
||||||
|
</Badge>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Self-hosted node
|
||||||
|
if (originalRow.remoteExitNodeId) {
|
||||||
|
return (
|
||||||
|
<Link
|
||||||
|
href={`/${originalRow.orgId}/settings/remote-exit-nodes/${originalRow.remoteExitNodeId}`}
|
||||||
|
>
|
||||||
|
<Button variant="outline">
|
||||||
|
{originalRow.exitNodeName}
|
||||||
|
<ArrowUpRight className="ml-2 h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback if no remoteExitNodeId
|
||||||
|
return <span>{originalRow.exitNodeName}</span>;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user