From 1087a95103978ee9d5359ca3696170c85412a053 Mon Sep 17 00:00:00 2001 From: Dale Driver Date: Fri, 7 Mar 2025 21:46:39 -0800 Subject: [PATCH 1/5] Update server.cjs (#17) Redact only sensitive info for logging --- src/backend/server.cjs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/backend/server.cjs b/src/backend/server.cjs index 94d9ab55..4d569cbf 100644 --- a/src/backend/server.cjs +++ b/src/backend/server.cjs @@ -22,8 +22,17 @@ io.on("connection", (socket) => { console.error("Invalid hostConfig received:", hostConfig); return; } - - console.log("Received hostConfig:", hostConfig); + + // Redact only sensitive info for logging + const safeHostConfig = { + ip: hostConfig.ip, + port: hostConfig.port, + user: hostConfig.user, + password: hostConfig.password ? '***REDACTED***' : undefined, + rsaKey: hostConfig.rsaKey ? '***REDACTED***' : undefined, + }; + + console.log("Received hostConfig:", safeHostConfig); const { ip, port, user, password, rsaKey } = hostConfig; const conn = new SSHClient(); @@ -100,4 +109,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 +}); -- 2.49.1 From 6e71cc8070d8696f2d56394c6b46f21ce6664fdc Mon Sep 17 00:00:00 2001 From: Karmaa Date: Sat, 8 Mar 2025 01:32:01 -0600 Subject: [PATCH 2/5] Changes to README.md for clear support paths. --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index c57af3eb..713474c1 100644 --- a/README.md +++ b/README.md @@ -40,6 +40,9 @@ Termix is an open-source forever free self-hosted SSH (other protocols planned, # Installation Visit the Termix [Wiki](https://github.com/LukeGus/Termix/wiki) for information on how to install Termix. You can also use these links to go directly to guide. [Docker](https://github.com/LukeGus/Termix/wiki/Docker) or [Manual](https://github.com/LukeGus/Termix/wiki/Manual). +# Support +If you need help with Termix, you can join the [Discord](https://discord.gg/jVQGdvHDrf) server and visit the support channel. You can also open an issue or open a pull request on the [GitHub](https://github.com/LukeGus/Termix/issues) repo. + # Show-off ![Demo Image](repo-images/DemoImage1.png) -- 2.49.1 From 0a81bea0750902da09b078933b7654505451b860 Mon Sep 17 00:00:00 2001 From: LukeGus Date: Sun, 9 Mar 2025 01:44:53 -0600 Subject: [PATCH 3/5] Switched to UTF-8 fixing non-english character bugs, revamped console logging system, fixed misc console errors, and fixed copy/paste. Warning: the terminal sizing in this version is very buggy so be warned. --- src/AddHostModal.jsx | 26 +++++++++--------------- src/App.jsx | 3 ++- src/Terminal.jsx | 46 +++++++++++++++++++++++++++++++----------- src/backend/server.cjs | 28 ++++++++++--------------- 4 files changed, 56 insertions(+), 47 deletions(-) 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 -- 2.49.1 From fb720c8c421321733963dcfa89fe46fe575ebff0 Mon Sep 17 00:00:00 2001 From: Karmaa Date: Sun, 9 Mar 2025 14:06:07 -0500 Subject: [PATCH 4/5] Fixed multi-line command issues with switching between split and not split. --- src/Terminal.jsx | 67 ++++++++++++++++++++++++------------------ src/backend/server.cjs | 4 +-- 2 files changed, 39 insertions(+), 32 deletions(-) diff --git a/src/Terminal.jsx b/src/Terminal.jsx index b26e8f90..04e5f72d 100644 --- a/src/Terminal.jsx +++ b/src/Terminal.jsx @@ -16,9 +16,7 @@ export const NewTerminal = forwardRef(({ hostConfig, isVisible }, ref) => { const terminalContainer = terminalRef.current; const parentContainer = terminalContainer?.parentElement; - if (!parentContainer || !isVisible) return; - - void parentContainer.offsetHeight; + if (!parentContainer || parentContainer.clientWidth === 0) return; const parentWidth = parentContainer.clientWidth; const parentHeight = parentContainer.clientHeight; @@ -26,12 +24,13 @@ export const NewTerminal = forwardRef(({ hostConfig, isVisible }, ref) => { terminalContainer.style.width = `${parentWidth}px`; terminalContainer.style.height = `${parentHeight}px`; - fitAddon.current.fit(); - - if (socketRef.current && terminalInstance.current) { - const { cols, rows } = terminalInstance.current; - socketRef.current.emit("resize", { cols, rows }); - } + requestAnimationFrame(() => { + fitAddon.current.fit(); + if (socketRef.current && terminalInstance.current) { + const { cols, rows } = terminalInstance.current; + socketRef.current.emit("resize", { cols, rows }); + } + }); }; useImperativeHandle(ref, () => ({ @@ -50,7 +49,6 @@ export const NewTerminal = forwardRef(({ hostConfig, isVisible }, ref) => { }, fontSize: 14, scrollback: 1000, - fontFamily: 'monospace', ignoreBracketedPasteMode: true, }); @@ -86,24 +84,29 @@ export const NewTerminal = forwardRef(({ hostConfig, isVisible }, ref) => { terminalInstance.current.write(decoder.decode(new Uint8Array(data))); }); + let isPasting = false; + terminalInstance.current.onData((data) => { socketRef.current.emit("data", data); }); 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); - }); + console.log("Event caled"); + if (isPasting) return; + + isPasting = true; + setTimeout(() => { + isPasting = false; + }, 200); + + if ((event.ctrlKey || event.metaKey) && event.key === "v") { + event.preventDefault(); + + navigator.clipboard.readText().then((text) => { + socketRef.current.emit("data", text); + }).catch((err) => { + console.error("Failed to read clipboard contents:", err); + }); return false; } return true; @@ -129,20 +132,21 @@ export const NewTerminal = forwardRef(({ hostConfig, isVisible }, ref) => { }, [hostConfig]); useEffect(() => { - if (isVisible) { - resizeTerminal(); - } + resizeTerminal(); }, [isVisible]); useEffect(() => { const terminalContainer = terminalRef.current; if (!terminalContainer) return; + const parentContainer = terminalContainer.parentElement; + if (!parentContainer) return; + const observer = new ResizeObserver(() => { resizeTerminal(); }); - observer.observe(terminalContainer); + observer.observe(parentContainer); return () => { observer.disconnect(); @@ -153,7 +157,12 @@ export const NewTerminal = forwardRef(({ hostConfig, isVisible }, ref) => {
); }); @@ -168,4 +177,4 @@ NewTerminal.propTypes = { port: PropTypes.string.isRequired, }).isRequired, isVisible: PropTypes.bool.isRequired, -}; \ No newline at end of file +}; diff --git a/src/backend/server.cjs b/src/backend/server.cjs index 03b07ed3..49fef97c 100644 --- a/src/backend/server.cjs +++ b/src/backend/server.cjs @@ -46,11 +46,10 @@ io.on("connection", (socket) => { socket.emit("error", err.message); return; } - 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) { @@ -71,7 +70,6 @@ io.on("connection", (socket) => { socket.on("resize", ({ cols, rows }) => { if (stream && stream.setWindow) { stream.setWindow(rows, cols, rows * 100, cols * 100); - console.log(`Terminal resized: cols=${cols}, rows=${rows}`); } }); -- 2.49.1 From 4f9aeb89fb4c63c6514fbad2bbbc47265d2478b8 Mon Sep 17 00:00:00 2001 From: Karmaa Date: Sun, 9 Mar 2025 18:20:43 -0500 Subject: [PATCH 5/5] Shifted terminal down and to the right so its not squished up in the top left corner. Fix scroll wheel sizing and looks. Prepared for this bug update release. --- src/AddHostModal.jsx | 17 ++++++++++++----- src/TabList.jsx | 16 ++++++---------- src/Terminal.jsx | 5 +++-- src/index.css | 20 ++++++++++++++++++++ 4 files changed, 41 insertions(+), 17 deletions(-) diff --git a/src/AddHostModal.jsx b/src/AddHostModal.jsx index 579f60e9..d73b78a3 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, FormHelperText } from '@mui/joy'; +import { Modal, Button, FormControl, FormLabel, Input, Stack, DialogTitle, DialogContent, ModalDialog, Select, Option } from '@mui/joy'; import theme from './theme'; const AddHostModal = ({ isHidden, form, setForm, handleAddHost, setIsAddHostHidden }) => { @@ -31,15 +31,22 @@ const AddHostModal = ({ isHidden, form, setForm, handleAddHost, setIsAddHostHidd setIsAddHostHidden(true)}> + width: "auto", + maxWidth: "90vw", + minWidth: "fit-content", + overflow: "hidden", + display: "flex", + flexDirection: "column", + alignItems: "center", + }} + > Add Host
- + Host Name 0; return ( -
+
{terminals.map((terminal, index) => { const isActive = terminal.id === activeTab; const isSplit = splitTabIds.includes(terminal.id); - - const isSplitButtonDisabled = isActive && !isSplitScreenActive || splitTabIds.length >= 3 && !isSplit; + const isSplitButtonDisabled = (isActive && !isSplitScreenActive) || (splitTabIds.length >= 3 && !isSplit); return (
@@ -20,8 +19,7 @@ function TabList({ terminals, activeTab, setActiveTab, closeTab, toggleSplit, sp onClick={() => setActiveTab(terminal.id)} disabled={isSplit} sx={{ - backgroundColor: - isActive ? theme.palette.general.primary : theme.palette.general.disabled, + backgroundColor: isActive ? theme.palette.general.primary : theme.palette.general.disabled, color: theme.palette.text.primary, "&:hover": { backgroundColor: theme.palette.general.secondary }, ":disabled": { backgroundColor: theme.palette.general.disabled }, @@ -40,9 +38,7 @@ function TabList({ terminals, activeTab, setActiveTab, closeTab, toggleSplit, sp onClick={() => toggleSplit(terminal.id)} disabled={isSplitButtonDisabled || isActive} sx={{ - backgroundColor: isSplit - ? theme.palette.general.primary - : theme.palette.general.tertiary, + backgroundColor: isSplit ? theme.palette.general.primary : theme.palette.general.tertiary, color: theme.palette.text.primary, ":disabled": { backgroundColor: theme.palette.general.disabled }, "&:hover": { backgroundColor: theme.palette.general.secondary }, @@ -58,7 +54,7 @@ function TabList({ terminals, activeTab, setActiveTab, closeTab, toggleSplit, sp {/* Close Tab Button */}