mirror of
https://github.com/tale/headplane.git
synced 2026-07-26 07:48:14 +00:00
fix: support user input when not specified for ssh
This commit is contained in:
@@ -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 <url>/key?v=116 to check the CORS headers
|
||||
@@ -202,9 +212,12 @@ export default function Page() {
|
||||
const [ipn, setIpn] = useState<TsWasmNet | null>(null);
|
||||
const [nodeKey, setNodeKey] = useState<string | null>(null);
|
||||
const { ipnDetails, sshDetails } = useLoaderData<typeof loader>();
|
||||
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 <UserPrompt hostname={sshDetails.hostname} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="w-screen h-screen bg-headplane-900">
|
||||
{ipn === null ? (
|
||||
|
||||
@@ -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 (
|
||||
<div className="flex items-center justify-center h-screen">
|
||||
<Card>
|
||||
<Card.Title>Enter Username</Card.Title>
|
||||
<Card.Text className="mb-4">
|
||||
Enter the username you want to use to connect to{' '}
|
||||
<Code>{hostname}</Code>
|
||||
{'. '}
|
||||
WebSSH follows the Headscale ACLs, so only permitted usernames will be
|
||||
able to connect.
|
||||
</Card.Text>
|
||||
<Input
|
||||
labelHidden
|
||||
type="text"
|
||||
label="Username"
|
||||
placeholder="Username"
|
||||
className="mb-2"
|
||||
onChange={setUsername}
|
||||
/>
|
||||
<Button
|
||||
variant="heavy"
|
||||
className="w-full"
|
||||
onPress={() => {
|
||||
// We can't use the navigate hook here as we need to do a
|
||||
// full page reload to ensure the SSH connection is established
|
||||
window.location.href = `${__PREFIX__}/ssh?hostname=${hostname}&username=${username}`;
|
||||
}}
|
||||
>
|
||||
Connect
|
||||
</Button>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -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`);
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user