Merge branch 'dev' into feat/login-page-customization

This commit is contained in:
Fred KISSIE
2025-11-15 06:32:03 +01:00
64 changed files with 2824 additions and 406 deletions

View File

@@ -232,6 +232,7 @@ export default function ExitNodesTable({
id: "actions",
cell: ({ row }) => {
const nodeRow = row.original;
const remoteExitNodeId = nodeRow.id;
return (
<div className="flex items-center justify-end gap-2">
<DropdownMenu>
@@ -242,6 +243,14 @@ export default function ExitNodesTable({
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<Link
className="block w-full"
href={`/${nodeRow.orgId}/settings/remote-exit-nodes/${remoteExitNodeId}`}
>
<DropdownMenuItem>
{t("viewSettings")}
</DropdownMenuItem>
</Link>
<DropdownMenuItem
onClick={() => {
setSelectedNode(nodeRow);
@@ -254,6 +263,14 @@ export default function ExitNodesTable({
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
<Link
href={`/${nodeRow.orgId}/settings/remote-exit-nodes/${remoteExitNodeId}`}
>
<Button variant={"secondary"} size="sm">
{t("edit")}
<ArrowRight className="ml-2 w-4 h-4" />
</Button>
</Link>
</div>
);
}

View File

@@ -0,0 +1,133 @@
"use client";
import { useState } from "react";
import {
SettingsContainer,
SettingsSection,
SettingsSectionBody,
SettingsSectionDescription,
SettingsSectionHeader,
SettingsSectionTitle
} from "@app/components/Settings";
import { Button } from "@app/components/ui/button";
import { createApiClient, formatAxiosError } from "@app/lib/api";
import { useEnvContext } from "@app/hooks/useEnvContext";
import { toast } from "@app/hooks/useToast";
import { useParams, useRouter } from "next/navigation";
import { AxiosResponse } from "axios";
import { useTranslations } from "next-intl";
import {
PickRemoteExitNodeDefaultsResponse,
QuickStartRemoteExitNodeResponse
} from "@server/routers/remoteExitNode/types";
import { useRemoteExitNodeContext } from "@app/hooks/useRemoteExitNodeContext";
import RegenerateCredentialsModal from "@app/components/RegenerateCredentialsModal";
import { useSubscriptionStatusContext } from "@app/hooks/useSubscriptionStatusContext";
import { useLicenseStatusContext } from "@app/hooks/useLicenseStatusContext";
import { build } from "@server/build";
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@app/components/ui/tooltip";
export default function CredentialsPage() {
const { env } = useEnvContext();
const api = createApiClient({ env });
const { orgId } = useParams();
const router = useRouter();
const t = useTranslations();
const { remoteExitNode } = useRemoteExitNodeContext();
const [modalOpen, setModalOpen] = useState(false);
const [credentials, setCredentials] = useState<PickRemoteExitNodeDefaultsResponse | null>(null);
const { licenseStatus, isUnlocked } = useLicenseStatusContext();
const subscription = useSubscriptionStatusContext();
const isSecurityFeatureDisabled = () => {
const isEnterpriseNotLicensed = build === "enterprise" && !isUnlocked();
const isSaasNotSubscribed =
build === "saas" && !subscription?.isSubscribed();
return isEnterpriseNotLicensed || isSaasNotSubscribed;
};
const handleConfirmRegenerate = async () => {
const response = await api.get<AxiosResponse<PickRemoteExitNodeDefaultsResponse>>(
`/org/${orgId}/pick-remote-exit-node-defaults`
);
const data = response.data.data;
setCredentials(data);
await api.put<AxiosResponse<QuickStartRemoteExitNodeResponse>>(
`/re-key/${orgId}/reGenerate-remote-exit-node-secret`,
{
remoteExitNodeId: remoteExitNode.remoteExitNodeId,
secret: data.secret,
}
);
toast({
title: t("credentialsSaved"),
description: t("credentialsSavedDescription")
});
router.refresh();
};
const getCredentials = () => {
if (credentials) {
return {
Id: remoteExitNode.remoteExitNodeId,
Secret: credentials.secret
};
}
return undefined;
};
return (
<SettingsContainer>
<SettingsSection>
<SettingsSectionHeader>
<SettingsSectionTitle>
{t("generatedcredentials")}
</SettingsSectionTitle>
<SettingsSectionDescription>
{t("regenerateCredentials")}
</SettingsSectionDescription>
</SettingsSectionHeader>
<SettingsSectionBody>
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<div className="inline-block">
<Button
onClick={() => setModalOpen(true)}
disabled={isSecurityFeatureDisabled()}
>
{t("regeneratecredentials")}
</Button>
</div>
</TooltipTrigger>
{isSecurityFeatureDisabled() && (
<TooltipContent side="top">
{t("featureDisabledTooltip")}
</TooltipContent>
)}
</Tooltip>
</TooltipProvider>
</SettingsSectionBody>
</SettingsSection>
<RegenerateCredentialsModal
open={modalOpen}
onOpenChange={setModalOpen}
type="remote-exit-node"
onConfirmRegenerate={handleConfirmRegenerate}
dashboardUrl={env.app.dashboardUrl}
credentials={getCredentials()}
/>
</SettingsContainer>
);
}

View File

@@ -1,3 +0,0 @@
export default function GeneralPage() {
return <></>;
}

View File

@@ -6,6 +6,8 @@ import { authCookieHeader } from "@app/lib/api/cookies";
import SettingsSectionTitle from "@app/components/SettingsSectionTitle";
import { getTranslations } from "next-intl/server";
import RemoteExitNodeProvider from "@app/providers/RemoteExitNodeProvider";
import { HorizontalTabs } from "@app/components/HorizontalTabs";
import ExitNodeInfoCard from "@app/components/ExitNodeInfoCard";
interface SettingsLayoutProps {
children: React.ReactNode;
@@ -31,6 +33,13 @@ export default async function SettingsLayout(props: SettingsLayoutProps) {
const t = await getTranslations();
const navItems = [
{
title: t('credentials'),
href: "/{orgId}/settings/remote-exit-nodes/{remoteExitNodeId}/credentials"
}
];
return (
<>
<SettingsSectionTitle
@@ -39,7 +48,10 @@ export default async function SettingsLayout(props: SettingsLayoutProps) {
/>
<RemoteExitNodeProvider remoteExitNode={remoteExitNode}>
<div className="space-y-6">{children}</div>
<div className="space-y-6">
<ExitNodeInfoCard />
<HorizontalTabs items={navItems}>{children}</HorizontalTabs>
</div>
</RemoteExitNodeProvider>
</>
);

View File

@@ -5,6 +5,6 @@ export default async function RemoteExitNodePage(props: {
}) {
const params = await props.params;
redirect(
`/${params.orgId}/settings/remote-exit-nodes/${params.remoteExitNodeId}/general`
`/${params.orgId}/settings/remote-exit-nodes/${params.remoteExitNodeId}/credentials`
);
}