From 6420a90d08b19e5e685801b2fe9cde287fb24fa7 Mon Sep 17 00:00:00 2001 From: Owen Date: Thu, 4 Jun 2026 16:21:30 -0700 Subject: [PATCH] Replace tab component --- src/app/ssh/SshClient.tsx | 135 ++++++++++++----------- src/components/newt-install-commands.tsx | 30 ++++- 2 files changed, 97 insertions(+), 68 deletions(-) diff --git a/src/app/ssh/SshClient.tsx b/src/app/ssh/SshClient.tsx index a9601738b..4ba1c9211 100644 --- a/src/app/ssh/SshClient.tsx +++ b/src/app/ssh/SshClient.tsx @@ -17,7 +17,7 @@ import { import Link from "next/link"; import { ExternalLink, Loader2, AlertCircle } from "lucide-react"; import { Alert, AlertDescription } from "@/components/ui/alert"; -import { cn } from "@app/lib/cn"; +import { HorizontalTabs } from "@app/components/HorizontalTabs"; import type { SignSshKeyResponse } from "@server/routers/ssh/types"; import { useTranslations } from "next-intl"; @@ -61,8 +61,6 @@ export default function SshClient({ const t = useTranslations(); - const [authTab, setAuthTab] = useState("password"); - function handleKeyFile(e: React.ChangeEvent) { const file = e.target.files?.[0]; if (!file) return; @@ -182,7 +180,10 @@ export default function SshClient({ } }, []); - function connect(override?: ConnectCredentials) { + function connect( + override?: ConnectCredentials, + authMethod: AuthTab = "password" + ) { setConnectError(null); setConnecting(true); @@ -194,10 +195,11 @@ export default function SshClient({ const username = override?.username ?? form.username; const password = - override?.password ?? (authTab === "password" ? form.password : ""); + override?.password ?? + (authMethod === "password" ? form.password : ""); const privateKey = override?.privateKey ?? - (authTab === "privateKey" ? form.privateKey : ""); + (authMethod === "privateKey" ? form.privateKey : ""); const certificate = override?.certificate; const proxyAddress = `${window.location.protocol === "https:" ? "wss" : "ws"}://${window.location.host}/gateway/ssh`; @@ -224,7 +226,7 @@ export default function SshClient({ ws.onopen = () => { // Send credentials as the first frame so the proxy can complete // SSH authentication before piping pty data. Stay in "connecting" - // state until the server responds — this prevents the flash to the + // state until the server responds - this prevents the flash to the // terminal page that would occur if we set connected=true here. ws.send( JSON.stringify({ @@ -260,7 +262,7 @@ export default function SshClient({ xtermRef.current?.write(msg.data); } else if (msg.type === "error") { if (!authConfirmed) { - // Auth-phase error — show in the login form. + // Auth-phase error - show in the login form. authErrorShown = true; setConnecting(false); setConnectError( @@ -281,13 +283,13 @@ export default function SshClient({ xtermRef.current?.write(evt.data); } } else if (evt.data instanceof Blob) { - evt.data.text().then((t) => { + evt.data.text().then((text) => { if (!authConfirmed) { authConfirmed = true; setConnecting(false); setConnected(true); } - xtermRef.current?.write(t); + xtermRef.current?.write(text); }); } }; @@ -426,31 +428,15 @@ export default function SshClient({ - {/* Tab row */} -
- {(["password", "privateKey"] as const).map( - (tab) => ( - - ) - )} -
- - {authTab === "password" && ( -
+ +
@@ -480,11 +465,31 @@ export default function SshClient({ } /> -
- )} +
+ {connectError && ( +

+ {connectError} +

+ )} - {authTab === "privateKey" && ( -
+ +
+
+ +

{t("sshPrivateKeyDisclaimer")}{" "} +

+ {connectError && ( +

+ {connectError} +

+ )} + + +
- )} - -
- {connectError && ( -

- {connectError} -

- )} - - -
+
diff --git a/src/components/newt-install-commands.tsx b/src/components/newt-install-commands.tsx index 422bc476d..ac8109eab 100644 --- a/src/components/newt-install-commands.tsx +++ b/src/components/newt-install-commands.tsx @@ -43,11 +43,14 @@ export function NewtSiteInstallCommands({ const t = useTranslations(); const [acceptClients, setAcceptClients] = useState(true); + const [allowPangolinSsh, setAllowPangolinSsh] = useState(true); const [platform, setPlatform] = useState("linux"); const [architecture, setArchitecture] = useState( () => getArchitectures(platform)[0] ); + const supportsSshOption = platform === "linux" || platform === "nixos"; + const acceptClientsFlag = !acceptClients ? " --disable-clients" : ""; const acceptClientsEnv = !acceptClients ? "\n - DISABLE_CLIENTS=true" @@ -57,6 +60,11 @@ export function NewtSiteInstallCommands({ --set newtInstances[0].acceptClients=true` : ""; + const disableSshFlag = + supportsSshOption && !allowPangolinSsh ? " --disable-ssh" : ""; + const runAsRootPrefix = + supportsSshOption && allowPangolinSsh ? "sudo " : ""; + const commandList: Record> = { linux: { Run: [ @@ -66,7 +74,7 @@ export function NewtSiteInstallCommands({ }, { title: t("run"), - command: `newt --id ${id} --secret ${secret} --endpoint ${endpoint}${acceptClientsFlag}` + command: `${runAsRootPrefix}newt --id ${id} --secret ${secret} --endpoint ${endpoint}${acceptClientsFlag}${disableSshFlag}` } ], "Systemd Service": [ @@ -86,6 +94,11 @@ PANGOLIN_ENDPOINT=${endpoint}${ ? ` DISABLE_CLIENTS=true` : "" + }${ + !allowPangolinSsh + ? ` +DISABLE_SSH=true` + : "" } EOF sudo chmod 600 /etc/newt/newt.env` @@ -205,7 +218,7 @@ WantedBy=default.target` }, nixos: { Flake: [ - `nix run 'nixpkgs#fosrl-newt' -- --id ${id} --secret ${secret} --endpoint ${endpoint}${acceptClientsFlag}` + `${runAsRootPrefix}nix run 'nixpkgs#fosrl-newt' -- --id ${id} --secret ${secret} --endpoint ${endpoint}${acceptClientsFlag}${disableSshFlag}` ] } }; @@ -273,6 +286,19 @@ WantedBy=default.target` label={t("siteAcceptClientConnections")} /> + {supportsSshOption && ( +
+ { + const value = checked as boolean; + setAllowPangolinSsh(value); + }} + label="Allow Pangolin SSH" + /> +
+ )}