mirror of
https://github.com/tale/headplane.git
synced 2026-08-28 07:57:03 +00:00
fix: resize cols and rows, not the other way around *sigh*
This commit is contained in:
@@ -23,6 +23,13 @@ export async function loader({
|
|||||||
request,
|
request,
|
||||||
context,
|
context,
|
||||||
}: LoaderFunctionArgs<LoadContext>) {
|
}: LoaderFunctionArgs<LoadContext>) {
|
||||||
|
if (!context.agents?.agentID()) {
|
||||||
|
throw data(
|
||||||
|
'WebSSH is only available with the Headplane agent integration',
|
||||||
|
400,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const session = await context.sessions.auth(request);
|
const session = await context.sessions.auth(request);
|
||||||
const user = session.get('user');
|
const user = session.get('user');
|
||||||
if (!user) {
|
if (!user) {
|
||||||
@@ -35,6 +42,7 @@ export async function loader({
|
|||||||
'v1/user',
|
'v1/user',
|
||||||
session.get('api_key')!,
|
session.get('api_key')!,
|
||||||
);
|
);
|
||||||
|
|
||||||
// MARK: This assumes that a user has authenticated with Headscale first
|
// MARK: This assumes that a user has authenticated with Headscale first
|
||||||
// Since the only way to enforce permissions via ACLs is to generate a
|
// Since the only way to enforce permissions via ACLs is to generate a
|
||||||
// pre-authkey which REQUIRES a user ID, meaning the user has to have
|
// pre-authkey which REQUIRES a user ID, meaning the user has to have
|
||||||
|
|||||||
Vendored
-12
@@ -46,18 +46,6 @@ interface XtermConfig {
|
|||||||
OnDisconnect: () => void;
|
OnDisconnect: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
// interface SSHTerminalConfig {
|
|
||||||
// writeFn: (data: string) => void;
|
|
||||||
// writeErrorFn: (error: string) => void;
|
|
||||||
// setReadFn: (cb: (input: string) => void) => void;
|
|
||||||
// rows: number;
|
|
||||||
// cols: number;
|
|
||||||
// timeoutSeconds?: number;
|
|
||||||
// onConnectionProgress: (message: string) => void;
|
|
||||||
// onConnected: () => void;
|
|
||||||
// onDone: () => void;
|
|
||||||
// }
|
|
||||||
|
|
||||||
interface SSHSession {
|
interface SSHSession {
|
||||||
Close(): boolean;
|
Close(): boolean;
|
||||||
Resize(rows: number, cols: number): boolean;
|
Resize(rows: number, cols: number): boolean;
|
||||||
|
|||||||
@@ -30,6 +30,17 @@ export default function XTerm({ ipn, username, hostname }: XTermProps) {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
pause();
|
pause();
|
||||||
|
if (!container.current) {
|
||||||
|
console.error('Container ref is not set');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't create a new terminal if one already exists
|
||||||
|
if (term.current) {
|
||||||
|
console.warn('Terminal already exists, skipping initialization');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const terminal = new xterm.Terminal({
|
const terminal = new xterm.Terminal({
|
||||||
allowProposedApi: true,
|
allowProposedApi: true,
|
||||||
cursorBlink: true,
|
cursorBlink: true,
|
||||||
@@ -52,24 +63,23 @@ export default function XTerm({ ipn, username, hostname }: XTermProps) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
terminal.unicode.activeVersion = '11';
|
terminal.unicode.activeVersion = '11';
|
||||||
const gl = new WebglAddon();
|
|
||||||
terminal.loadAddon(gl);
|
|
||||||
|
|
||||||
const fit = new FitAddon();
|
const fit = new FitAddon();
|
||||||
terminal.loadAddon(fit);
|
terminal.loadAddon(fit);
|
||||||
|
|
||||||
|
const gl = new WebglAddon();
|
||||||
|
terminal.loadAddon(gl);
|
||||||
|
|
||||||
gl.onContextLoss(() => {
|
gl.onContextLoss(() => {
|
||||||
console.warn('WebGL context lost, falling back to canvas rendering');
|
console.warn('WebGL context lost, falling back to canvas rendering');
|
||||||
gl.dispose();
|
gl.dispose();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
fit.fit();
|
||||||
|
term.current = terminal;
|
||||||
terminal.open(container.current!);
|
terminal.open(container.current!);
|
||||||
terminal.focus();
|
terminal.focus();
|
||||||
term.current = terminal;
|
|
||||||
|
|
||||||
let ro: ResizeObserver | null = null;
|
|
||||||
let onUnload: ((e: Event) => void) | null = null;
|
let onUnload: ((e: Event) => void) | null = null;
|
||||||
|
|
||||||
const session = ipn.OpenSSH(hostname, username, {
|
const session = ipn.OpenSSH(hostname, username, {
|
||||||
Rows: terminal.rows,
|
Rows: terminal.rows,
|
||||||
Cols: terminal.cols,
|
Cols: terminal.cols,
|
||||||
@@ -103,8 +113,7 @@ export default function XTerm({ ipn, username, hostname }: XTermProps) {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const parent = container.current?.ownerDocument.defaultView ?? window;
|
const ro = new ResizeObserver(() => {
|
||||||
ro = new parent.ResizeObserver(() => {
|
|
||||||
if (term.current) {
|
if (term.current) {
|
||||||
setIsResizing(true);
|
setIsResizing(true);
|
||||||
fit.fit();
|
fit.fit();
|
||||||
@@ -112,12 +121,10 @@ export default function XTerm({ ipn, username, hostname }: XTermProps) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (container.current) {
|
ro.observe(container.current);
|
||||||
ro.observe(container.current);
|
|
||||||
}
|
|
||||||
|
|
||||||
terminal.onResize(({ cols, rows }) => {
|
terminal.onResize(({ cols, rows }) => {
|
||||||
session.Resize(rows, cols);
|
console.log(`Terminal resized to ${cols}x${rows}`);
|
||||||
|
session.Resize(cols, rows);
|
||||||
});
|
});
|
||||||
|
|
||||||
terminal.onData((data) => {
|
terminal.onData((data) => {
|
||||||
@@ -126,12 +133,25 @@ export default function XTerm({ ipn, username, hostname }: XTermProps) {
|
|||||||
|
|
||||||
onUnload = (_) => session.Close();
|
onUnload = (_) => session.Close();
|
||||||
parent.addEventListener('unload', onUnload);
|
parent.addEventListener('unload', onUnload);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
if (onUnload) {
|
||||||
|
parent.removeEventListener('unload', onUnload);
|
||||||
|
}
|
||||||
|
|
||||||
|
session.Close();
|
||||||
|
ro?.disconnect();
|
||||||
|
terminal.dispose();
|
||||||
|
term.current = null;
|
||||||
|
inputRef.current = null;
|
||||||
|
console.log('SSH session closed and terminal disposed');
|
||||||
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative w-full h-full group">
|
<>
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
<div className="mx-auto h-screen flex items-center justify-center">
|
<div className="absolute w-screen z-50 mx-auto h-screen flex items-center justify-center">
|
||||||
<Loader2 className="animate-spin size-10 text-headplane-50" />
|
<Loader2 className="animate-spin size-10 text-headplane-50" />
|
||||||
</div>
|
</div>
|
||||||
) : undefined}
|
) : undefined}
|
||||||
@@ -159,6 +179,6 @@ export default function XTerm({ ipn, username, hostname }: XTermProps) {
|
|||||||
Failed to connect to SSH session
|
Failed to connect to SSH session
|
||||||
</div>
|
</div>
|
||||||
) : undefined}
|
) : undefined}
|
||||||
</div>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -150,7 +150,7 @@ func (s *SSHSession) Resize(rows, cols int) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return s.Pty.WindowChange(rows, cols)
|
return s.Pty.WindowChange(cols, rows)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Closes the SSH session.
|
// Closes the SSH session.
|
||||||
|
|||||||
Reference in New Issue
Block a user