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
+136
View File
@@ -22,3 +22,139 @@ dist-ssr
*.njsproj *.njsproj
*.sln *.sln
*.sw? *.sw?
# 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
# 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
.env.local
.env.development.local
.env.test.local
.env.production.local
.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/
+2 -2
View File
@@ -19,8 +19,8 @@ FROM nginx:alpine
RUN apk add --no-cache nodejs npm RUN apk add --no-cache nodejs npm
COPY docker/nginx.conf /etc/nginx/nginx.conf COPY docker/nginx.conf /etc/nginx/nginx.conf
COPY --from=frontend-build /app/frontend/dist /usr/share/nginx/html COPY --from=frontend-build /app/frontend/dist /usr/share/nginx/html
COPY --from=backend-build /backend /backend COPY --from=backend-build src/backend/ /src/backend/
COPY --from=backend-build /backend/entrypoint.sh /backend/entrypoint.sh COPY --from=backend-build src/backend/entrypoint.sh /src/backend/entrypoint.sh
# Configure start-up # Configure start-up
RUN chmod +x /backend/entrypoint.sh RUN chmod +x /backend/entrypoint.sh
-149
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/
+33 -25
View File
@@ -7,8 +7,6 @@
.sidebar { .sidebar {
display: flex; display: flex;
flex-shrink: 1;
flex-grow: 1;
flex-direction: column; flex-direction: column;
gap: 0.5em; gap: 0.5em;
position: fixed; position: fixed;
@@ -24,39 +22,49 @@
.topbar { .topbar {
display: flex; display: flex;
flex-shrink: 0;
flex-grow: 1;
flex-direction: row; flex-direction: row;
position: fixed; position: fixed;
justify-content: space-between;
align-items: center; align-items: center;
text-align: center; padding: 8px;
padding: 30px;
background-color: #323232; background-color: #323232;
top: 0; top: 0;
left: 14em; left: 14em;
right: 0; right: 0;
width: calc(100% - 14em); height: 48px;
min-height: 36px;
height: auto;
font-size: 16px;
gap: 0.5em;
overflow-x: auto; overflow-x: auto;
gap: 8px;
border-bottom: 1px solid #404040;
} }
.topbar button { .tab-item {
padding: 0.5em 1em; display: flex;
background-color: #444; align-items: center;
border: none; gap: 4px;
background: #404040;
border-radius: 4px; border-radius: 4px;
color: white; padding: 4px;
cursor: pointer; flex-shrink: 0; /* Prevent tabs from shrinking */
white-space: nowrap;
} }
.topbar button.active-tab { .tab-item button {
background-color: #1a1a1a; padding: 6px 12px;
border-bottom: 2px solid white; 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 { .terminal-tab {
@@ -66,6 +74,8 @@
right: 0; right: 0;
bottom: 0; bottom: 0;
display: none; display: none;
width: 100%;
height: 100%;
} }
.terminal-tab.active { .terminal-tab.active {
@@ -74,8 +84,6 @@
.add-host { .add-host {
display: flex; display: flex;
flex-shrink: 1;
flex-grow: 1;
flex-direction: column; flex-direction: column;
justify-content: space-between; justify-content: space-between;
position: fixed; position: fixed;
@@ -125,7 +133,7 @@
.terminal-wrapper { .terminal-wrapper {
position: fixed; position: fixed;
top: 96px; top: 64px;
left: 14em; left: 14em;
right: 0; right: 0;
bottom: 0; bottom: 0;
+11 -1
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 ( return (
<> <>
<div className="sidebar"> <div className="sidebar">
@@ -39,13 +47,15 @@ function App() {
</div> </div>
<div className="topbar"> <div className="topbar">
{terminals.map((terminal) => ( {terminals.map((terminal) => (
<div key={terminal.id} className="tab-item">
<button <button
key={terminal.id}
onClick={() => setActiveTab(terminal.id)} onClick={() => setActiveTab(terminal.id)}
className={activeTab === terminal.id ? "active-tab" : ""} className={activeTab === terminal.id ? "active-tab" : ""}
> >
{terminal.title} {terminal.title}
</button> </button>
<button className="tab-close" onClick={() => closeTab(terminal.id)}>×</button>
</div>
))} ))}
</div> </div>
<div className="terminal-wrapper"> <div className="terminal-wrapper">
+28 -26
View File
@@ -1,4 +1,3 @@
// Terminal.jsx
import { useEffect, useRef } from "react"; import { useEffect, useRef } from "react";
import { Terminal } from "@xterm/xterm"; import { Terminal } from "@xterm/xterm";
import { FitAddon } from "@xterm/addon-fit"; import { FitAddon } from "@xterm/addon-fit";
@@ -15,10 +14,15 @@ export function NewTerminal({ hostConfig }) {
// Initialize terminal // Initialize terminal
const terminal = new Terminal({ const terminal = new Terminal({
cursorBlink: true, cursorBlink: true,
cursorStyle: "block", theme: {
theme: { background: "#1a1a1a", foreground: "#ffffff", cursor: "#ffffff" }, background: "#1a1a1a",
foreground: "#ffffff",
cursor: "#ffffff",
},
fontSize: 14, fontSize: 14,
scrollback: 1000, scrollback: 1000,
rendererType: "canvas",
allowTransparency: true,
}); });
// Initialize FitAddon for auto-sizing // Initialize FitAddon for auto-sizing
@@ -28,20 +32,11 @@ export function NewTerminal({ hostConfig }) {
// Open terminal in the container // Open terminal in the container
terminal.open(terminalRef.current); terminal.open(terminalRef.current);
// Apply fit after terminal is fully initialized // Resize function (Restoring your original logic)
setTimeout(() => {
fitAddon.fit();
resizeTerminal();
}, 100);
// Focus on terminal and reset layout
terminal.focus();
// Resize terminal to fit the container
const resizeTerminal = () => { const resizeTerminal = () => {
const terminalContainer = terminalRef.current; const terminalContainer = terminalRef.current;
const sidebarWidth = 14 * 16; // Sidebar width in pixels 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 availableWidth = window.innerWidth - sidebarWidth;
const availableHeight = window.innerHeight - topbarHeight; const availableHeight = window.innerHeight - topbarHeight;
@@ -51,42 +46,49 @@ export function NewTerminal({ hostConfig }) {
fitAddon.fit(); fitAddon.fit();
const { cols, rows } = terminal; const { cols, rows } = terminal;
// Emit new terminal size to the backend
if (socket) { if (socket) {
socket.emit("resize", { cols, rows }); socket.emit("resize", { cols, rows });
console.log(`Terminal resized: cols=${cols}, rows=${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); window.addEventListener("resize", resizeTerminal);
// Write initial connection message // Write initial connection message
terminal.write("\r\n*** Connecting to backend ***\r\n"); terminal.write("\r\n*** Connecting to backend ***\r\n");
// Create the socket connection with the provided hostConfig // Create socket connection
const socket = io("http://localhost:8081"); 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 const socket = io(wsUrl);
// Emit hostConfig to start SSH connection
socket.on("connect", () => {
fitAddon.fit(); fitAddon.fit();
resizeTerminal(); // Ensure proper size on connection
const { cols, rows } = terminal; const { cols, rows } = terminal;
socket.emit("connectToHost", cols, rows, hostConfig); socket.emit("connectToHost", cols, rows, hostConfig);
// Handle socket connection events
socket.on("connect", () => {
terminal.write("\r\n*** Connected to backend ***\r\n"); terminal.write("\r\n*** Connected to backend ***\r\n");
// Send keystrokes to the backend
terminal.onKey((key) => { terminal.onKey((key) => {
socket.emit("data", key.key); socket.emit("data", key.key);
}); });
// Display output from the backend
socket.on("data", (data) => { socket.on("data", (data) => {
terminal.write(data); terminal.write(data);
}); });
// Handle disconnection
socket.on("disconnect", () => { socket.on("disconnect", () => {
terminal.write("\r\n*** Disconnected from backend ***\r\n"); terminal.write("\r\n*** Disconnected from backend ***\r\n");
}); });
@@ -98,7 +100,7 @@ export function NewTerminal({ hostConfig }) {
window.removeEventListener("resize", resizeTerminal); window.removeEventListener("resize", resizeTerminal);
socket.disconnect(); socket.disconnect();
}; };
}, [hostConfig]); // Re-run effect when hostConfig changes }, [hostConfig]);
return ( return (
<div <div
+26 -27
View File
@@ -13,19 +13,9 @@ const io = socketIo(server, {
io.on("connection", (socket) => { io.on("connection", (socket) => {
console.log("New socket connection established"); console.log("New socket connection established");
let stream = null;
let currentCols = 80; let currentCols = 80;
let currentRows = 24; 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) => { socket.on("connectToHost", (cols, rows, hostConfig) => {
if (!hostConfig || !hostConfig.ip || !hostConfig.user || !hostConfig.password || !hostConfig.port) { if (!hostConfig || !hostConfig.ip || !hostConfig.user || !hostConfig.password || !hostConfig.port) {
@@ -36,19 +26,13 @@ io.on("connection", (socket) => {
console.log("Received hostConfig:", hostConfig); console.log("Received hostConfig:", hostConfig);
const { ip, port, user, password } = 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(); const conn = new SSHClient();
conn conn
.on("ready", function () { .on("ready", function () {
console.log("SSH connection established"); console.log("SSH connection established");
socket.emit("data", "\r\n*** SSH CONNECTION ESTABLISHED ***\r\n"); 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) { if (err) {
console.error("Error opening SSH shell:", err); console.error("Error opening SSH shell:", err);
return socket.emit( return socket.emit(
@@ -58,20 +42,35 @@ io.on("connection", (socket) => {
} }
stream = newStream; 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) { socket.on("data", function (data) {
stream.write(data); stream.write(data);
}); });
stream // Resize SSH terminal when client resizes
.on("data", function (d) { socket.on("resize", ({ cols, rows }) => {
socket.emit("data", d.toString("binary")); if (stream && stream.setWindow) {
}) stream.setWindow(rows, cols, rows * 100, cols * 100);
.on("close", function () { console.log(`Terminal resized: cols=${cols}, rows=${rows}`);
console.log("SSH stream closed"); }
conn.end();
}); });
// Auto-send initial terminal size to backend
socket.emit("resize", { cols, rows });
}); });
}) })
.on("close", function () { .on("close", function () {