mirror of
https://github.com/tale/headplane.git
synced 2026-09-04 19:25:43 +00:00
fix: support user input when not specified for ssh
This commit is contained in:
@@ -9,7 +9,6 @@ import {
|
|||||||
useLoaderData,
|
useLoaderData,
|
||||||
useSubmit,
|
useSubmit,
|
||||||
} from 'react-router';
|
} from 'react-router';
|
||||||
import { useEventSource } from 'remix-utils/sse/react';
|
|
||||||
import { LoadContext } from '~/server';
|
import { LoadContext } from '~/server';
|
||||||
import { Machine, PreAuthKey, User } from '~/types';
|
import { Machine, PreAuthKey, User } from '~/types';
|
||||||
import { useLiveData } from '~/utils/live-data';
|
import { useLiveData } from '~/utils/live-data';
|
||||||
@@ -19,6 +18,7 @@ import { eq } from 'drizzle-orm';
|
|||||||
import wasm from '~/hp_ssh.wasm?url';
|
import wasm from '~/hp_ssh.wasm?url';
|
||||||
import { EphemeralNodeInsert, ephemeralNodes } from '~/server/db/schema';
|
import { EphemeralNodeInsert, ephemeralNodes } from '~/server/db/schema';
|
||||||
import '~/wasm_exec';
|
import '~/wasm_exec';
|
||||||
|
import UserPrompt from './user-prompt';
|
||||||
|
|
||||||
export const shouldRevalidate: ShouldRevalidateFunction = () => {
|
export const shouldRevalidate: ShouldRevalidateFunction = () => {
|
||||||
return false;
|
return false;
|
||||||
@@ -88,8 +88,18 @@ export async function loader({
|
|||||||
const qp = new URL(request.url).searchParams;
|
const qp = new URL(request.url).searchParams;
|
||||||
const username = qp.get('username') || undefined;
|
const username = qp.get('username') || undefined;
|
||||||
const hostname = qp.get('hostname') || undefined;
|
const hostname = qp.get('hostname') || undefined;
|
||||||
if (!username || !hostname) {
|
if (!hostname) {
|
||||||
throw data('Missing required parameters: username, hostname', 400);
|
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
|
// 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 [ipn, setIpn] = useState<TsWasmNet | null>(null);
|
||||||
const [nodeKey, setNodeKey] = useState<string | null>(null);
|
const [nodeKey, setNodeKey] = useState<string | null>(null);
|
||||||
const { ipnDetails, sshDetails } = useLoaderData<typeof loader>();
|
const { ipnDetails, sshDetails } = useLoaderData<typeof loader>();
|
||||||
const ping = useEventSource('/ssh/ping', { event: 'ping' });
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (!ipnDetails) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
pause();
|
pause();
|
||||||
const go = new Go(); // Go is defined by wasm_exec.js
|
const go = new Go(); // Go is defined by wasm_exec.js
|
||||||
WebAssembly.instantiateStreaming(fetch(wasm), go.importObject).then(
|
WebAssembly.instantiateStreaming(fetch(wasm), go.importObject).then(
|
||||||
@@ -249,6 +262,10 @@ export default function Page() {
|
|||||||
);
|
);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
if (!sshDetails.username) {
|
||||||
|
return <UserPrompt hostname={sshDetails.hostname} />;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-screen h-screen bg-headplane-900">
|
<div className="w-screen h-screen bg-headplane-900">
|
||||||
{ipn === null ? (
|
{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
|
// Delete from the Headscale nodes list and then from the database
|
||||||
const promises = toPrune.map((node) => {
|
const promises = toPrune.map((node) => {
|
||||||
return async () => {
|
return async () => {
|
||||||
log.info('api', `Pruning node ${node.name}`);
|
log.debug('api', `Pruning node ${node.name}`);
|
||||||
await context.client.delete(
|
await context.client.delete(
|
||||||
`v1/node/${node.id}`,
|
`v1/node/${node.id}`,
|
||||||
session.get('api_key')!,
|
session.get('api_key')!,
|
||||||
@@ -50,7 +50,7 @@ export async function pruneEphemeralNodes({
|
|||||||
await context.db
|
await context.db
|
||||||
.delete(ephemeralNodes)
|
.delete(ephemeralNodes)
|
||||||
.where(eq(ephemeralNodes.node_key, node.nodeKey));
|
.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