mirror of
https://github.com/tale/headplane.git
synced 2026-08-20 18:02:17 +00:00
feat: support headplane compiled without ssh
This commit is contained in:
+4
-2
@@ -6,8 +6,10 @@ node_modules
|
|||||||
.env
|
.env
|
||||||
/app/hp_ssh.wasm
|
/app/hp_ssh.wasm
|
||||||
/app/wasm_exec.js
|
/app/wasm_exec.js
|
||||||
|
|
||||||
|
/public/hp_ssh.wasm
|
||||||
|
/public/wasm_exec.js
|
||||||
|
|
||||||
/docs/.vitepress/dist/
|
/docs/.vitepress/dist/
|
||||||
/docs/.vitepress/cache/
|
/docs/.vitepress/cache/
|
||||||
|
|
||||||
>>>>>>> origin/main
|
|
||||||
/.direnv
|
/.direnv
|
||||||
|
|||||||
+4
-2
@@ -8,6 +8,7 @@ import {
|
|||||||
useNavigation,
|
useNavigation,
|
||||||
} from 'react-router';
|
} from 'react-router';
|
||||||
import '@fontsource-variable/inter';
|
import '@fontsource-variable/inter';
|
||||||
|
import { ExternalScripts } from 'remix-utils/external-scripts';
|
||||||
import { ErrorPopup } from '~/components/Error';
|
import { ErrorPopup } from '~/components/Error';
|
||||||
import ProgressBar from '~/components/ProgressBar';
|
import ProgressBar from '~/components/ProgressBar';
|
||||||
import ToastProvider from '~/components/ToastProvider';
|
import ToastProvider from '~/components/ToastProvider';
|
||||||
@@ -38,16 +39,17 @@ export function Layout({ children }: { readonly children: React.ReactNode }) {
|
|||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charSet="utf-8" />
|
<meta charSet="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta content="width=device-width, initial-scale=1" name="viewport" />
|
||||||
<Meta />
|
<Meta />
|
||||||
<Links />
|
<Links />
|
||||||
<link rel="icon" href="favicon.ico" />
|
<link href="favicon.ico" rel="icon" />
|
||||||
</head>
|
</head>
|
||||||
<body className="overscroll-none dark:bg-headplane-900 dark:text-headplane-50">
|
<body className="overscroll-none dark:bg-headplane-900 dark:text-headplane-50">
|
||||||
{children}
|
{children}
|
||||||
<ToastProvider queue={toastQueue} />
|
<ToastProvider queue={toastQueue} />
|
||||||
<ScrollRestoration />
|
<ScrollRestoration />
|
||||||
<Scripts />
|
<Scripts />
|
||||||
|
<ExternalScripts />
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
</LiveDataProvider>
|
</LiveDataProvider>
|
||||||
|
|||||||
+75
-42
@@ -1,4 +1,5 @@
|
|||||||
/** biome-ignore-all lint/correctness/noNestedComponentDefinitions: Wtf? */
|
/** biome-ignore-all lint/correctness/noNestedComponentDefinitions: Wtf? */
|
||||||
|
|
||||||
import { faker } from '@faker-js/faker';
|
import { faker } from '@faker-js/faker';
|
||||||
import { eq } from 'drizzle-orm';
|
import { eq } from 'drizzle-orm';
|
||||||
import { Loader2 } from 'lucide-react';
|
import { Loader2 } from 'lucide-react';
|
||||||
@@ -6,16 +7,17 @@ import { useEffect, useState } from 'react';
|
|||||||
import {
|
import {
|
||||||
ActionFunctionArgs,
|
ActionFunctionArgs,
|
||||||
data,
|
data,
|
||||||
|
LinksFunction,
|
||||||
LoaderFunctionArgs,
|
LoaderFunctionArgs,
|
||||||
ShouldRevalidateFunction,
|
ShouldRevalidateFunction,
|
||||||
useLoaderData,
|
useLoaderData,
|
||||||
useSubmit,
|
useSubmit,
|
||||||
} from 'react-router';
|
} from 'react-router';
|
||||||
|
import { ExternalScriptsHandle } from 'remix-utils/external-scripts';
|
||||||
import { LoadContext } from '~/server';
|
import { LoadContext } from '~/server';
|
||||||
import { EphemeralNodeInsert, ephemeralNodes } from '~/server/db/schema';
|
import { EphemeralNodeInsert, ephemeralNodes } from '~/server/db/schema';
|
||||||
import { Machine, PreAuthKey, User } from '~/types';
|
import { Machine, PreAuthKey, User } from '~/types';
|
||||||
import { useLiveData } from '~/utils/live-data';
|
import { useLiveData } from '~/utils/live-data';
|
||||||
import '~/wasm_exec';
|
|
||||||
import UserPrompt from './user-prompt';
|
import UserPrompt from './user-prompt';
|
||||||
import XTerm from './xterm.client';
|
import XTerm from './xterm.client';
|
||||||
|
|
||||||
@@ -27,6 +29,19 @@ export async function loader({
|
|||||||
request,
|
request,
|
||||||
context,
|
context,
|
||||||
}: LoaderFunctionArgs<LoadContext>) {
|
}: LoaderFunctionArgs<LoadContext>) {
|
||||||
|
const origin = new URL(request.url).origin;
|
||||||
|
const assets = ['/wasm_exec.js', '/hp_ssh.wasm'];
|
||||||
|
const missing: string[] = [];
|
||||||
|
|
||||||
|
for (const file of assets) {
|
||||||
|
const res = await fetch(`${origin}${file}`, { method: 'HEAD' });
|
||||||
|
if (!res.ok) missing.push(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (missing.length > 0) {
|
||||||
|
throw data('WebSSH is not configured in this build.', 405);
|
||||||
|
}
|
||||||
|
|
||||||
if (!context.agents?.agentID()) {
|
if (!context.agents?.agentID()) {
|
||||||
throw data(
|
throw data(
|
||||||
'WebSSH is only available with the Headplane agent integration',
|
'WebSSH is only available with the Headplane agent integration',
|
||||||
@@ -199,6 +214,26 @@ export async function action({
|
|||||||
.where(eq(ephemeralNodes.auth_key, authKey));
|
.where(eq(ephemeralNodes.auth_key, authKey));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const links: LinksFunction = () => [
|
||||||
|
{
|
||||||
|
rel: 'preload',
|
||||||
|
href: '/hp_ssh.wasm',
|
||||||
|
as: 'fetch',
|
||||||
|
type: 'application/wasm',
|
||||||
|
crossOrigin: 'anonymous',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const handle: ExternalScriptsHandle = {
|
||||||
|
scripts: [
|
||||||
|
{
|
||||||
|
src: '/wasm_exec.js',
|
||||||
|
crossOrigin: 'anonymous',
|
||||||
|
preload: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
export default function Page() {
|
export default function Page() {
|
||||||
const submit = useSubmit();
|
const submit = useSubmit();
|
||||||
const { pause } = useLiveData();
|
const { pause } = useLiveData();
|
||||||
@@ -214,48 +249,46 @@ export default function Page() {
|
|||||||
|
|
||||||
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(
|
||||||
import('~/hp_ssh.wasm?url').then(({ default: url }) => {
|
fetch('/hp_ssh.wasm'),
|
||||||
WebAssembly.instantiateStreaming(fetch(url), go.importObject).then(
|
go.importObject,
|
||||||
(value) => {
|
).then((value) => {
|
||||||
go.run(value.instance);
|
go.run(value.instance);
|
||||||
const handle = TsWasmNet(ipnDetails, {
|
const handle = TsWasmNet(ipnDetails, {
|
||||||
NotifyState: (state) => {
|
NotifyState: (state) => {
|
||||||
console.log('State changed:', state);
|
console.log('State changed:', state);
|
||||||
if (state === 'Running') {
|
if (state === 'Running') {
|
||||||
setIpn(handle);
|
setIpn(handle);
|
||||||
}
|
}
|
||||||
},
|
|
||||||
NotifyNetMap: (netmap) => {
|
|
||||||
// Only set NodeKey if it is not already set and then
|
|
||||||
// also dispatch that to the backend to track the
|
|
||||||
// ephemeral node.
|
|
||||||
//
|
|
||||||
// We open an SSE connection to the backend
|
|
||||||
// so that when the connection is closed,
|
|
||||||
// the backend can delete the ephemeral node.
|
|
||||||
if (nodeKey === null) {
|
|
||||||
setNodeKey(netmap.NodeKey);
|
|
||||||
submit(
|
|
||||||
{
|
|
||||||
node_key: netmap.NodeKey,
|
|
||||||
auth_key: ipnDetails.PreAuthKey,
|
|
||||||
},
|
|
||||||
{ method: 'POST' },
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
NotifyBrowseToURL: (url) => {
|
|
||||||
console.log('Browse to URL:', url);
|
|
||||||
},
|
|
||||||
NotifyPanicRecover: (message) => {
|
|
||||||
console.error('Panic recover:', message);
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
handle.Start();
|
|
||||||
},
|
},
|
||||||
);
|
NotifyNetMap: (netmap) => {
|
||||||
|
// Only set NodeKey if it is not already set and then
|
||||||
|
// also dispatch that to the backend to track the
|
||||||
|
// ephemeral node.
|
||||||
|
//
|
||||||
|
// We open an SSE connection to the backend
|
||||||
|
// so that when the connection is closed,
|
||||||
|
// the backend can delete the ephemeral node.
|
||||||
|
if (nodeKey === null) {
|
||||||
|
setNodeKey(netmap.NodeKey);
|
||||||
|
submit(
|
||||||
|
{
|
||||||
|
node_key: netmap.NodeKey,
|
||||||
|
auth_key: ipnDetails.PreAuthKey,
|
||||||
|
},
|
||||||
|
{ method: 'POST' },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
NotifyBrowseToURL: (url) => {
|
||||||
|
console.log('Browse to URL:', url);
|
||||||
|
},
|
||||||
|
NotifyPanicRecover: (message) => {
|
||||||
|
console.error('Panic recover:', message);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
handle.Start();
|
||||||
});
|
});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ cd "$ROOT_DIR" || exit 1
|
|||||||
|
|
||||||
APP_DIR="$ROOT_DIR/app"
|
APP_DIR="$ROOT_DIR/app"
|
||||||
BUILD_DIR="$ROOT_DIR/build"
|
BUILD_DIR="$ROOT_DIR/build"
|
||||||
|
PUBLIC_DIR="$ROOT_DIR/public"
|
||||||
|
|
||||||
BUILD_WASM=0
|
BUILD_WASM=0
|
||||||
BUILD_APP=0
|
BUILD_APP=0
|
||||||
@@ -19,7 +20,7 @@ SKIP_PATH_CHECKS=0
|
|||||||
SKIP_PNPM_PRUNE=0
|
SKIP_PNPM_PRUNE=0
|
||||||
APP_INSTALL_ONLY=0
|
APP_INSTALL_ONLY=0
|
||||||
|
|
||||||
WASM_OUTPUT="$APP_DIR/hp_ssh.wasm"
|
WASM_OUTPUT="$PUBLIC_DIR/hp_ssh.wasm"
|
||||||
AGENT_OUTPUT="$BUILD_DIR/hp_agent"
|
AGENT_OUTPUT="$BUILD_DIR/hp_agent"
|
||||||
FAKE_SHELL_OUTPUT="$BUILD_DIR/hp_fake_sh"
|
FAKE_SHELL_OUTPUT="$BUILD_DIR/hp_fake_sh"
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user