diff --git a/src/AddHostModal.jsx b/src/AddHostModal.jsx index b7c65dc6..579f60e9 100644 --- a/src/AddHostModal.jsx +++ b/src/AddHostModal.jsx @@ -1,6 +1,6 @@ import PropTypes from 'prop-types'; import { CssVarsProvider } from '@mui/joy/styles'; -import { Modal, Button, FormControl, FormLabel, Input, Stack, DialogTitle, DialogContent, ModalDialog, Select, Option } from '@mui/joy'; +import { Modal, Button, FormControl, FormLabel, Input, Stack, DialogTitle, DialogContent, ModalDialog, Select, Option, FormHelperText } from '@mui/joy'; import theme from './theme'; const AddHostModal = ({ isHidden, form, setForm, handleAddHost, setIsAddHostHidden }) => { @@ -54,47 +54,42 @@ const AddHostModal = ({ isHidden, form, setForm, handleAddHost, setIsAddHostHidd setForm({ ...form, name: e.target.value })} - required={false} sx={{ backgroundColor: theme.palette.general.primary, color: theme.palette.text.primary, }} /> - + Host IP setForm({ ...form, ip: e.target.value })} required - error={!form.ip ? "Please provide an IP address" : ""} sx={{ backgroundColor: theme.palette.general.primary, color: theme.palette.text.primary, }} /> - + Host User setForm({ ...form, user: e.target.value })} required - error={form.user ? "" : "Please provide a username"} sx={{ backgroundColor: theme.palette.general.primary, color: theme.palette.text.primary, }} /> - + Authentication Method {form.authMethod === 'password' && ( - + Host Password setForm({ ...form, password: e.target.value })} required - error={form.password ? "" : "Please provide a password"} sx={{ backgroundColor: theme.palette.general.primary, color: theme.palette.text.primary, @@ -127,13 +121,12 @@ const AddHostModal = ({ isHidden, form, setForm, handleAddHost, setIsAddHostHidd )} {form.authMethod === 'rsaKey' && ( - + RSA Key )} - + 65535}> Host Port 65535 ? "Port must be between 1 and 65535" : ""} sx={{ backgroundColor: theme.palette.general.primary, color: theme.palette.text.primary, diff --git a/src/App.jsx b/src/App.jsx index 2a943d96..6c1a6474 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -21,6 +21,7 @@ function App() { user: "", password: "", port: 22, + authMethod: "Select Auth", }); const [isLaunchpadOpen, setIsLaunchpadOpen] = useState(false); const [splitTabIds, setSplitTabIds] = useState([]); @@ -90,7 +91,7 @@ function App() { user: form.user, password: form.authMethod === 'password' ? form.password : undefined, rsaKey: form.authMethod === 'rsaKey' ? form.rsaKey : undefined, - port: Number(form.port), + port: String(form.port), }, terminalRef: null, }; diff --git a/src/Terminal.jsx b/src/Terminal.jsx index e0e677a4..b26e8f90 100644 --- a/src/Terminal.jsx +++ b/src/Terminal.jsx @@ -50,8 +50,8 @@ export const NewTerminal = forwardRef(({ hostConfig, isVisible }, ref) => { }, fontSize: 14, scrollback: 1000, - rendererType: "canvas", - allowTransparency: true, + fontFamily: 'monospace', + ignoreBracketedPasteMode: true, }); terminalInstance.current.loadAddon(fitAddon.current); @@ -63,8 +63,6 @@ export const NewTerminal = forwardRef(({ hostConfig, isVisible }, ref) => { terminalInstance.current.focus(); }, 50); - terminalInstance.current.write("\r\n*** Connecting to backend ***\r\n"); - const socket = io( window.location.hostname === "localhost" ? "http://localhost:8081" @@ -81,23 +79,47 @@ export const NewTerminal = forwardRef(({ hostConfig, isVisible }, ref) => { resizeTerminal(); const { cols, rows } = terminalInstance.current; socket.emit("connectToHost", cols, rows, hostConfig); - terminalInstance.current.write("\r\n*** Connected to backend ***\r\n"); }); socket.on("data", (data) => { - terminalInstance.current.write(data); + const decoder = new TextDecoder("utf-8"); + terminalInstance.current.write(decoder.decode(new Uint8Array(data))); }); - socket.on("disconnect", () => { - terminalInstance.current.write("\r\n*** Disconnected from backend ***\r\n"); + terminalInstance.current.onData((data) => { + socketRef.current.emit("data", data); }); - terminalInstance.current.onKey(({ key }) => { - socket.emit("data", key); + terminalInstance.current.attachCustomKeyEventHandler((event) => { + if ( + (event.ctrlKey && event.key === "v") || + (event.metaKey && event.key === "v") || + (event.shiftKey && event.key === "Insert") + ) { + navigator.clipboard + .readText() + .then((text) => { + socketRef.current.emit("data", text); + }) + .catch((err) => { + console.error("Failed to read clipboard contents:", err); + }); + return false; + } + return true; }); - socket.on("connect_error", (err) => { - terminalInstance.current.write(`\r\n*** Error: ${err.message} ***\r\n`); + terminalInstance.current.onKey(({ domEvent }) => { + if (domEvent.key === "c" && (domEvent.ctrlKey || domEvent.metaKey)) { + const selection = terminalInstance.current.getSelection(); + if (selection) { + navigator.clipboard.writeText(selection); + } + } + }); + + socket.on("error", (err) => { + terminalInstance.current.write(`\r\n*** Error: ${err} ***\r\n`); }); return () => { diff --git a/src/backend/server.cjs b/src/backend/server.cjs index 4d569cbf..03b07ed3 100644 --- a/src/backend/server.cjs +++ b/src/backend/server.cjs @@ -22,7 +22,7 @@ io.on("connection", (socket) => { console.error("Invalid hostConfig received:", hostConfig); return; } - + // Redact only sensitive info for logging const safeHostConfig = { ip: hostConfig.ip, @@ -31,7 +31,7 @@ io.on("connection", (socket) => { password: hostConfig.password ? '***REDACTED***' : undefined, rsaKey: hostConfig.rsaKey ? '***REDACTED***' : undefined, }; - + console.log("Received hostConfig:", safeHostConfig); const { ip, port, user, password, rsaKey } = hostConfig; @@ -39,25 +39,22 @@ io.on("connection", (socket) => { conn .on("ready", function () { console.log("SSH connection established"); - socket.emit("data", "\r\n*** SSH CONNECTION ESTABLISHED ***\r\n"); conn.shell({ term: "xterm-256color" }, function (err, newStream) { if (err) { - console.error("Error opening SSH shell:", err); - return socket.emit( - "data", - "\r\n*** SSH SHELL ERROR: " + err.message + " ***\r\n" - ); + console.error("Error:", err.message); + socket.emit("error", err.message); + return; } - stream = newStream; + stream = newStream; // Set initial terminal size stream.setWindow(rows, cols, rows * 100, cols * 100); console.log(`Initial terminal size: cols=${cols}, rows=${rows}`); // Pipe SSH output to client stream.on("data", function (data) { - socket.emit("data", data.toString("binary")); + socket.emit("data", data); }); stream.on("close", function () { @@ -84,14 +81,11 @@ io.on("connection", (socket) => { }) .on("close", function () { console.log("SSH connection closed"); - socket.emit("data", "\r\n*** SSH CONNECTION CLOSED ***\r\n"); + socket.emit("error", "SSH connection closed"); }) .on("error", function (err) { - console.error("SSH connection error:", err); - socket.emit( - "data", - "\r\n*** SSH CONNECTION ERROR: " + err.message + " ***\r\n" - ); + console.error("Error:", err.message); + socket.emit("error", err.message); }) .connect({ host: ip, @@ -109,4 +103,4 @@ io.on("connection", (socket) => { server.listen(8081, '0.0.0.0', () => { console.log("Server is running on port 8081"); -}); +}); \ No newline at end of file