Fixed tab spacing, nano reset, and coloring.

This commit is contained in:
Karmaa
2025-02-09 01:48:59 -06:00
parent 3b0eddca27
commit 33060eb1e4
7 changed files with 245 additions and 239 deletions

149
src/.gitignore vendored
View File

@@ -1,149 +0,0 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# TypeScript v1 declaration files
typings/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
.env.test
# parcel-bundler cache (https://parceljs.org/)
.cache
# Next.js build output
.next
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and *not* Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# production
/build
# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.bash_history
.bashrc
.init_done
.profile
.sudo_as_admin_successful
.wget-hsts
.git-credentials
.docker/
.bash_logout
# VSCode Files
.vscode-server/
# Configs
.config/
# .dotnet
.dotnet/
# .local
.local/

View File

@@ -7,8 +7,6 @@
.sidebar {
display: flex;
flex-shrink: 1;
flex-grow: 1;
flex-direction: column;
gap: 0.5em;
position: fixed;
@@ -24,39 +22,49 @@
.topbar {
display: flex;
flex-shrink: 0;
flex-grow: 1;
flex-direction: row;
position: fixed;
justify-content: space-between;
align-items: center;
text-align: center;
padding: 30px;
padding: 8px;
background-color: #323232;
top: 0;
left: 14em;
right: 0;
width: calc(100% - 14em);
min-height: 36px;
height: auto;
font-size: 16px;
gap: 0.5em;
height: 48px;
overflow-x: auto;
gap: 8px;
border-bottom: 1px solid #404040;
}
.topbar button {
padding: 0.5em 1em;
background-color: #444;
border: none;
.tab-item {
display: flex;
align-items: center;
gap: 4px;
background: #404040;
border-radius: 4px;
color: white;
cursor: pointer;
white-space: nowrap;
padding: 4px;
flex-shrink: 0; /* Prevent tabs from shrinking */
}
.topbar button.active-tab {
background-color: #1a1a1a;
border-bottom: 2px solid white;
.tab-item button {
padding: 6px 12px;
background: none;
border: none;
color: #fff;
cursor: pointer;
}
.tab-close {
padding: 2px 6px !important;
border-radius: 50%;
}
.tab-close:hover {
background: #ff5555 !important;
}
.active-tab {
background: #1a1a1a !important;
}
.terminal-tab {
@@ -66,6 +74,8 @@
right: 0;
bottom: 0;
display: none;
width: 100%;
height: 100%;
}
.terminal-tab.active {
@@ -74,8 +84,6 @@
.add-host {
display: flex;
flex-shrink: 1;
flex-grow: 1;
flex-direction: column;
justify-content: space-between;
position: fixed;
@@ -125,7 +133,7 @@
.terminal-wrapper {
position: fixed;
top: 96px;
top: 64px;
left: 14em;
right: 0;
bottom: 0;

View File

@@ -31,6 +31,14 @@ function App() {
}
};
const closeTab = (id) => {
const newTerminals = terminals.filter(t => t.id !== id);
setTerminals(newTerminals);
if (activeTab === id) {
setActiveTab(newTerminals[0]?.id || null);
}
};
return (
<>
<div className="sidebar">
@@ -39,13 +47,15 @@ function App() {
</div>
<div className="topbar">
{terminals.map((terminal) => (
<button
key={terminal.id}
onClick={() => setActiveTab(terminal.id)}
className={activeTab === terminal.id ? "active-tab" : ""}
>
{terminal.title}
</button>
<div key={terminal.id} className="tab-item">
<button
onClick={() => setActiveTab(terminal.id)}
className={activeTab === terminal.id ? "active-tab" : ""}
>
{terminal.title}
</button>
<button className="tab-close" onClick={() => closeTab(terminal.id)}>×</button>
</div>
))}
</div>
<div className="terminal-wrapper">
@@ -54,7 +64,7 @@ function App() {
key={terminal.id}
className={`terminal-tab ${terminal.id === activeTab ? "active" : ""}`}
>
{terminal.hostConfig && <NewTerminal hostConfig={terminal.hostConfig} />}
{terminal.hostConfig && <NewTerminal hostConfig={terminal.hostConfig}/>}
</div>
))}
</div>

View File

@@ -1,4 +1,3 @@
// Terminal.jsx
import { useEffect, useRef } from "react";
import { Terminal } from "@xterm/xterm";
import { FitAddon } from "@xterm/addon-fit";
@@ -15,10 +14,15 @@ export function NewTerminal({ hostConfig }) {
// Initialize terminal
const terminal = new Terminal({
cursorBlink: true,
cursorStyle: "block",
theme: { background: "#1a1a1a", foreground: "#ffffff", cursor: "#ffffff" },
theme: {
background: "#1a1a1a",
foreground: "#ffffff",
cursor: "#ffffff",
},
fontSize: 14,
scrollback: 1000,
rendererType: "canvas",
allowTransparency: true,
});
// Initialize FitAddon for auto-sizing
@@ -28,20 +32,11 @@ export function NewTerminal({ hostConfig }) {
// Open terminal in the container
terminal.open(terminalRef.current);
// Apply fit after terminal is fully initialized
setTimeout(() => {
fitAddon.fit();
resizeTerminal();
}, 100);
// Focus on terminal and reset layout
terminal.focus();
// Resize terminal to fit the container
// Resize function (Restoring your original logic)
const resizeTerminal = () => {
const terminalContainer = terminalRef.current;
const sidebarWidth = 14 * 16; // Sidebar width in pixels
const topbarHeight = 96; // Topbar height in pixels
const topbarHeight = 64; // Topbar height in pixels
const availableWidth = window.innerWidth - sidebarWidth;
const availableHeight = window.innerHeight - topbarHeight;
@@ -51,42 +46,49 @@ export function NewTerminal({ hostConfig }) {
fitAddon.fit();
const { cols, rows } = terminal;
// Emit new terminal size to the backend
if (socket) {
socket.emit("resize", { cols, rows });
console.log(`Terminal resized: cols=${cols}, rows=${rows}`);
}
};
// Handle window resize events
// Ensure correct sizing on start
setTimeout(() => {
fitAddon.fit();
resizeTerminal();
}, 50); // Small delay to ensure proper initialization
// Focus on terminal after initialization
terminal.focus();
// Listen for window resize events
window.addEventListener("resize", resizeTerminal);
// Write initial connection message
terminal.write("\r\n*** Connecting to backend ***\r\n");
// Create the socket connection with the provided hostConfig
const socket = io("http://localhost:8081");
// Create socket connection
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
const wsUrl = `${protocol}//${window.location.host}/ws/`;
// Emit the hostConfig to the server to start SSH connection
fitAddon.fit();
const { cols, rows } = terminal;
socket.emit("connectToHost", cols, rows, hostConfig);
const socket = io(wsUrl);
// Handle socket connection events
// Emit hostConfig to start SSH connection
socket.on("connect", () => {
fitAddon.fit();
resizeTerminal(); // Ensure proper size on connection
const { cols, rows } = terminal;
socket.emit("connectToHost", cols, rows, hostConfig);
terminal.write("\r\n*** Connected to backend ***\r\n");
// Send keystrokes to the backend
terminal.onKey((key) => {
socket.emit("data", key.key);
});
// Display output from the backend
socket.on("data", (data) => {
terminal.write(data);
});
// Handle disconnection
socket.on("disconnect", () => {
terminal.write("\r\n*** Disconnected from backend ***\r\n");
});
@@ -98,7 +100,7 @@ export function NewTerminal({ hostConfig }) {
window.removeEventListener("resize", resizeTerminal);
socket.disconnect();
};
}, [hostConfig]); // Re-run effect when hostConfig changes
}, [hostConfig]);
return (
<div

View File

@@ -13,19 +13,9 @@ const io = socketIo(server, {
io.on("connection", (socket) => {
console.log("New socket connection established");
let stream = null;
let currentCols = 80;
let currentRows = 24;
let stream = null;
socket.on("resize", ({ cols, rows }) => {
console.log(`Terminal resized: cols=${cols}, rows=${rows}`);
currentCols = cols;
currentRows = rows;
if (stream && stream.setWindow) {
stream.setWindow(rows, cols, rows * 100, cols * 100);
console.log(`SSH terminal resized to: cols=${cols}, rows=${rows}`);
}
});
socket.on("connectToHost", (cols, rows, hostConfig) => {
if (!hostConfig || !hostConfig.ip || !hostConfig.user || !hostConfig.password || !hostConfig.port) {
@@ -36,19 +26,13 @@ io.on("connection", (socket) => {
console.log("Received hostConfig:", hostConfig);
const { ip, port, user, password } = hostConfig;
if (!ip || !port || !user || !password) {
socket.emit("data", "\r\n*** Missing required connection data ***\r\n");
return;
}
console.log("Preparing to connect to host:", hostConfig);
const conn = new SSHClient();
conn
.on("ready", function () {
console.log("SSH connection established");
socket.emit("data", "\r\n*** SSH CONNECTION ESTABLISHED ***\r\n");
conn.shell(function (err, newStream) {
conn.shell({ term: "xterm-256color" }, function (err, newStream) {
if (err) {
console.error("Error opening SSH shell:", err);
return socket.emit(
@@ -58,20 +42,35 @@ io.on("connection", (socket) => {
}
stream = newStream;
stream.setWindow(currentRows, currentCols, currentRows * 100, currentCols * 100);
// 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"));
});
stream.on("close", function () {
console.log("SSH stream closed");
conn.end();
});
// Send keystrokes from terminal to SSH
socket.on("data", function (data) {
stream.write(data);
});
stream
.on("data", function (d) {
socket.emit("data", d.toString("binary"));
})
.on("close", function () {
console.log("SSH stream closed");
conn.end();
});
// Resize SSH terminal when client resizes
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}`);
}
});
// Auto-send initial terminal size to backend
socket.emit("resize", { cols, rows });
});
})
.on("close", function () {