diff --git a/app/routes/ssh/console.tsx b/app/routes/ssh/console.tsx index e5ccf00..7e47379 100644 --- a/app/routes/ssh/console.tsx +++ b/app/routes/ssh/console.tsx @@ -9,7 +9,6 @@ import { useLoaderData, useSubmit, } from 'react-router'; -import { useEventSource } from 'remix-utils/sse/react'; import { LoadContext } from '~/server'; import { Machine, PreAuthKey, User } from '~/types'; import { useLiveData } from '~/utils/live-data'; @@ -19,6 +18,7 @@ import { eq } from 'drizzle-orm'; import wasm from '~/hp_ssh.wasm?url'; import { EphemeralNodeInsert, ephemeralNodes } from '~/server/db/schema'; import '~/wasm_exec'; +import UserPrompt from './user-prompt'; export const shouldRevalidate: ShouldRevalidateFunction = () => { return false; @@ -88,8 +88,18 @@ export async function loader({ const qp = new URL(request.url).searchParams; const username = qp.get('username') || undefined; const hostname = qp.get('hostname') || undefined; - if (!username || !hostname) { - throw data('Missing required parameters: username, hostname', 400); + if (!hostname) { + throw data('Missing required parameter: hostname', 400); + } + + if (!username) { + return { + ipnDetails: undefined, + sshDetails: { + username, + hostname, + }, + }; } // We're making a request to /key?v=116 to check the CORS headers @@ -202,9 +212,12 @@ export default function Page() { const [ipn, setIpn] = useState(null); const [nodeKey, setNodeKey] = useState(null); const { ipnDetails, sshDetails } = useLoaderData(); - const ping = useEventSource('/ssh/ping', { event: 'ping' }); useEffect(() => { + if (!ipnDetails) { + return; + } + pause(); const go = new Go(); // Go is defined by wasm_exec.js WebAssembly.instantiateStreaming(fetch(wasm), go.importObject).then( @@ -249,6 +262,10 @@ export default function Page() { ); }, []); + if (!sshDetails.username) { + return ; + } + return (
{ipn === null ? ( diff --git a/app/routes/ssh/user-prompt.tsx b/app/routes/ssh/user-prompt.tsx new file mode 100644 index 0000000..bbf160d --- /dev/null +++ b/app/routes/ssh/user-prompt.tsx @@ -0,0 +1,47 @@ +import { useState } from 'react'; +import Button from '~/components/Button'; +import Card from '~/components/Card'; +import Code from '~/components/Code'; +import Input from '~/components/Input'; + +interface UserPromptProps { + hostname: string; +} + +export default function UserPrompt({ hostname }: UserPromptProps) { + const [username, setUsername] = useState(''); + + return ( +
+ + Enter Username + + Enter the username you want to use to connect to{' '} + {hostname} + {'. '} + WebSSH follows the Headscale ACLs, so only permitted usernames will be + able to connect. + + + + +
+ ); +} diff --git a/app/server/db/pruner.ts b/app/server/db/pruner.ts index 57adafd..815e393 100644 --- a/app/server/db/pruner.ts +++ b/app/server/db/pruner.ts @@ -41,7 +41,7 @@ export async function pruneEphemeralNodes({ // Delete from the Headscale nodes list and then from the database const promises = toPrune.map((node) => { return async () => { - log.info('api', `Pruning node ${node.name}`); + log.debug('api', `Pruning node ${node.name}`); await context.client.delete( `v1/node/${node.id}`, session.get('api_key')!, @@ -50,7 +50,7 @@ export async function pruneEphemeralNodes({ await context.db .delete(ephemeralNodes) .where(eq(ephemeralNodes.node_key, node.nodeKey)); - log.info('api', `Node ${node.name} pruned successfully`); + log.debug('api', `Node ${node.name} pruned successfully`); }; });